/* ============================================================
   shared.jsx — icons, date/status utilities, small components
   ============================================================ */
const { useState, useEffect, useRef, useMemo, useCallback } = React;

/* ---------- "today" anchor for the prototype ---------- */
const TODAY = new Date('2026-06-01T00:00:00');
const NEAR_DAYS = 60;

/* ---------- icons (1.6 stroke, 20px line set) ---------- */
function Icon({ d, size = 19, fill, stroke = 1.6, children, style }) {
  return (
    <svg className="ico" width={size} height={size} viewBox="0 0 24 24"
         fill={fill || 'none'} stroke={fill ? 'none' : 'currentColor'}
         strokeWidth={stroke} strokeLinecap="round" strokeLinejoin="round"
         style={style} aria-hidden="true">
      {children || <path d={d} />}
    </svg>
  );
}
const I = {
  dash:   (p) => <Icon {...p}><path d="M4 13h6V4H4zM14 20h6v-9h-6zM14 4v4h6V4zM4 20h6v-3H4z"/></Icon>,
  list:   (p) => <Icon {...p}><path d="M8 6h12M8 12h12M8 18h12M3.5 6h.01M3.5 12h.01M3.5 18h.01"/></Icon>,
  calendar:(p)=> <Icon {...p}><rect x="3" y="4.5" width="18" height="16" rx="2.5"/><path d="M3 9h18M8 2.5v4M16 2.5v4"/></Icon>,
  bell:   (p) => <Icon {...p}><path d="M18 8.5a6 6 0 1 0-12 0c0 6-2.5 7.5-2.5 7.5h17S18 14.5 18 8.5M10.3 20.5a2 2 0 0 0 3.4 0"/></Icon>,
  plus:   (p) => <Icon {...p}><path d="M12 5v14M5 12h14"/></Icon>,
  search: (p) => <Icon {...p}><circle cx="11" cy="11" r="7"/><path d="m20 20-3.2-3.2"/></Icon>,
  chevR:  (p) => <Icon {...p}><path d="m9 6 6 6-6 6"/></Icon>,
  chevD:  (p) => <Icon {...p}><path d="m6 9 6 6 6-6"/></Icon>,
  arrowL: (p) => <Icon {...p}><path d="M19 12H5M11 6l-6 6 6 6"/></Icon>,
  close:  (p) => <Icon {...p}><path d="M6 6l12 12M18 6 6 18"/></Icon>,
  check:  (p) => <Icon {...p}><path d="M20 6 9 17l-5-5"/></Icon>,
  filter: (p) => <Icon {...p}><path d="M3 5h18l-7 8v6l-4 2v-8z"/></Icon>,
  sliders:(p) => <Icon {...p}><path d="M4 6h10M18 6h2M4 12h2M10 12h10M4 18h7M15 18h5"/><circle cx="16" cy="6" r="2"/><circle cx="8" cy="12" r="2"/><circle cx="13" cy="18" r="2"/></Icon>,
  clock:  (p) => <Icon {...p}><circle cx="12" cy="12" r="8.5"/><path d="M12 7.5V12l3 2"/></Icon>,
  qr:     (p) => <Icon {...p}><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><path d="M14 14h3v3M20 14v.01M14 20v.01M20 20v.01M17 17h.01M20 17h.01M17 20h3"/></Icon>,
  beaker: (p) => <Icon {...p}><path d="M9 3h6M10 3v6.5L5 19a2 2 0 0 0 1.8 3h10.4A2 2 0 0 0 19 19l-5-9.5V3M7.5 14h9"/></Icon>,
  micro:  (p) => <Icon {...p}><path d="M6 21h13M8.5 21a5.5 5.5 0 0 0 5.4-6.6M9.5 17 8 18l-2.2-2.2 1.5-1.5M14.2 14.6l1.7-1.7a1.4 1.4 0 0 0 0-2l-3.6-3.6a1.4 1.4 0 0 0-2 0L8.6 9l5.6 5.6ZM13 5.5l1.2-1.2a1 1 0 0 1 1.4 0l1.6 1.6a1 1 0 0 1 0 1.4L17 8.5"/></Icon>,
  flask:  (p) => <Icon {...p}><path d="M9 3h6M10 3v6.5L5 19a2 2 0 0 0 1.8 3h10.4A2 2 0 0 0 19 19l-5-9.5V3M7.5 14h9"/></Icon>,
  history:(p) => <Icon {...p}><path d="M3 12a9 9 0 1 0 3-6.7L3 8M3 3v5h5M12 7.5V12l3.5 2"/></Icon>,
  warning:(p) => <Icon {...p}><path d="M12 3.5 22 19a1 1 0 0 1-.9 1.5H2.9A1 1 0 0 1 2 19L12 3.5M12 9v5M12 17.5v.01"/></Icon>,
  shield: (p) => <Icon {...p}><path d="M12 3 5 6v5c0 4.5 3 7.5 7 9 4-1.5 7-4.5 7-9V6z"/><path d="m9 12 2 2 4-4"/></Icon>,
  trend:  (p) => <Icon {...p}><path d="M3 17l5-5 3.5 3.5L20 7M15 7h5v5"/></Icon>,
  copy:   (p) => <Icon {...p}><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h8"/></Icon>,
  edit:   (p) => <Icon {...p}><path d="M4 20h4L19 9a2 2 0 0 0-3-3L5 17z"/><path d="M14 7l3 3"/></Icon>,
  download:(p)=> <Icon {...p}><path d="M12 4v11M7 11l5 5 5-5M5 20h14"/></Icon>,
  sun:    (p) => <Icon {...p}><circle cx="12" cy="12" r="4.2"/><path d="M12 2v2M12 20v2M4 12H2M22 12h-2M5 5l1.5 1.5M17.5 17.5 19 19M19 5l-1.5 1.5M6.5 17.5 5 19"/></Icon>,
  moon:   (p) => <Icon {...p}><path d="M20 14.5A8 8 0 0 1 9.5 4 7 7 0 1 0 20 14.5"/></Icon>,
  menu:   (p) => <Icon {...p}><path d="M4 7h16M4 12h16M4 17h16"/></Icon>,
  grid:   (p) => <Icon {...p}><rect x="3" y="3" width="7.5" height="7.5" rx="1.5"/><rect x="13.5" y="3" width="7.5" height="7.5" rx="1.5"/><rect x="3" y="13.5" width="7.5" height="7.5" rx="1.5"/><rect x="13.5" y="13.5" width="7.5" height="7.5" rx="1.5"/></Icon>,
  pin:    (p) => <Icon {...p}><path d="M12 21s7-6.2 7-11a7 7 0 1 0-14 0c0 4.8 7 11 7 11"/><circle cx="12" cy="10" r="2.5"/></Icon>,
  tag:    (p) => <Icon {...p}><path d="M3 12V4a1 1 0 0 1 1-1h8l9 9-9 9z"/><circle cx="7.5" cy="7.5" r="1.4"/></Icon>,
  refresh:(p) => <Icon {...p}><path d="M3 12a9 9 0 0 1 15-6.7L21 8M21 3v5h-5M21 12a9 9 0 0 1-15 6.7L3 16M3 21v-5h5"/></Icon>,
  spark:  (p) => <Icon {...p}><path d="M12 3v4M12 17v4M3 12h4M17 12h4M5.6 5.6l2.8 2.8M15.6 15.6l2.8 2.8M18.4 5.6l-2.8 2.8M8.4 15.6l-2.8 2.8"/></Icon>,
  dots:   (p) => <Icon {...p}><circle cx="5" cy="12" r="1.6" fill="currentColor" stroke="none"/><circle cx="12" cy="12" r="1.6" fill="currentColor" stroke="none"/><circle cx="19" cy="12" r="1.6" fill="currentColor" stroke="none"/></Icon>,
  building:(p)=> <Icon {...p}><rect x="4" y="3" width="16" height="18" rx="1.5"/><path d="M9 7h.01M15 7h.01M9 11h.01M15 11h.01M9 15h.01M15 15h.01M10 21v-3h4v3"/></Icon>,
  lock:   (p) => <Icon {...p}><rect x="4.5" y="10.5" width="15" height="10" rx="2.2"/><path d="M8 10.5V7a4 4 0 0 1 8 0v3.5M12 14.5v2.5"/></Icon>,
  unlock: (p) => <Icon {...p}><rect x="4.5" y="10.5" width="15" height="10" rx="2.2"/><path d="M8 10.5V7a4 4 0 0 1 7.8-1.2M12 14.5v2.5"/></Icon>,
  logout: (p) => <Icon {...p}><path d="M15 4h3a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2h-3M10 17l5-5-5-5M15 12H3"/></Icon>,
  user:   (p) => <Icon {...p}><circle cx="12" cy="8" r="4"/><path d="M5 21a7 7 0 0 1 14 0"/></Icon>,
  finger: (p) => <Icon {...p}><path d="M12 4a6 6 0 0 1 6 6v2M6 10a6 6 0 0 1 3-5.2M6 12v1a9 9 0 0 0 1.2 4.5M12 9a3 3 0 0 0-3 3v1a12 12 0 0 0 1 5M12 12v2a8 8 0 0 0 1.6 5M15 11v3a14 14 0 0 0 .8 4.6"/></Icon>,
  eye:    (p) => <Icon {...p}><path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7Z"/><circle cx="12" cy="12" r="3"/></Icon>,
  eyeoff: (p) => <Icon {...p}><path d="M3 3l18 18M10.5 5.2A10 10 0 0 1 12 5c6.5 0 10 7 10 7a18 18 0 0 1-3 3.8M6.3 6.4A18 18 0 0 0 2 12s3.5 7 10 7a10 10 0 0 0 3.3-.5M9.5 9.5a3 3 0 0 0 4.2 4.2"/></Icon>,
  scan:   (p) => <Icon {...p}><path d="M4 8V6a2 2 0 0 1 2-2h2M16 4h2a2 2 0 0 1 2 2v2M20 16v2a2 2 0 0 1-2 2h-2M8 20H6a2 2 0 0 1-2-2v-2M4 12h16"/></Icon>,
  trash:  (p) => <Icon {...p}><path d="M4 7h16M9 7V5a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2M6 7l1 13a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1l1-13M10 11v6M14 11v6"/></Icon>,
  pie:    (p) => <Icon {...p}><path d="M12 3v9l7.5 4.5"/><circle cx="12" cy="12" r="9"/></Icon>,
  addbox: (p) => <Icon {...p}><rect x="3" y="3" width="18" height="18" rx="5.5"/><path d="M12 8.2v7.6M8.2 12h7.6"/></Icon>,
  thermo: (p) => <Icon {...p}><path d="M14 14.76V5a2 2 0 0 0-4 0v9.76a4 4 0 1 0 4 0z"/><path d="M12 8v6.5"/></Icon>,
  wrench: (p) => <Icon {...p}><path d="M14.5 6.5a3.8 3.8 0 0 1 4.9 4.7l-.4 1 .9.9-2.6 2.6-.9-.9-1 .4a3.8 3.8 0 0 1-4.7-4.9L4.5 4.6 6 3l5.7 5.7"/><path d="m13 11-8 8"/></Icon>,
};

/* ---------- department metadata ---------- */
const DEPT_META = {
  'Blood bank':            { abbr: 'BLD', th: 'ธนาคารเลือด',            hue: 18,  short: 'Blood Bank' },
  'Chemistry':             { abbr: 'CHE', th: 'เคมีคลินิก',              hue: 250, short: 'Chemistry' },
  'Hematology&Microscopy': { abbr: 'HEM', th: 'โลหิตวิทยา/จุลทรรศน์',    hue: 330, short: 'Hematology' },
  'Microbiology':          { abbr: 'MIC', th: 'จุลชีววิทยา',            hue: 150, short: 'Microbiology' },
  'Biomolecular':          { abbr: 'MOL', th: 'ชีวโมเลกุล',             hue: 195, short: 'Biomolecular' },
  'Serology':              { abbr: 'SER', th: 'ภูมิคุ้มกัน/ซีโรโลยี',    hue: 70,  short: 'Serology' },
  'Pathology':             { abbr: 'CYT', th: 'พยาธิ/เซลล์วิทยา',        hue: 300, short: 'Pathology' },
};
/* real responsible person per department (head / section MT) */
const DEPT_OWNER = {
  'Blood bank':            'นางพานิช ศรีเนตร · นักวิทยาศาสตร์การแพทย์ชำนาญการ',
  'Chemistry':             'ทนพญ. สุภาวดี เบ้าทอง',
  'Hematology&Microscopy': 'ทนพญ. สุวรรณี ไสวดี',
  'Microbiology':          'ทนพญ. วิรัตยา พลเดช',
  'Biomolecular':          'ทนพ. พีรภัทร ภักตะภา',
  'Serology':              'ทนพ. พงศกร วันทาพงษ์',
  'Pathology':             'นายเดชา บุตรมาศ · เจ้าพนักงานวิทยาศาสตร์การแพทย์ชำนาญงาน',
};
/* standard location label tied to the 7 real departments (system-wide) */
function deptLoc(deptName) {
  const m = DEPT_META[deptName] || {};
  return `งาน${m.th || deptName} (${m.short || deptName})`;
}
function deptColor(dept, l = 0.62, c = 0.13) {
  const m = DEPT_META[dept];
  const hue = m ? m.hue : 60;
  return `oklch(${l} ${c} ${hue})`;
}
function deptSoft(dept) {
  const m = DEPT_META[dept];
  const hue = m ? m.hue : 60;
  return `oklch(0.94 0.04 ${hue})`;
}

/* ---------- date / status utilities ---------- */
const THMONTH = ['ม.ค.','ก.พ.','มี.ค.','เม.ย.','พ.ค.','มิ.ย.','ก.ค.','ส.ค.','ก.ย.','ต.ค.','พ.ย.','ธ.ค.'];
function parseD(s) { return s ? new Date(s + 'T00:00:00') : null; }
function fmtDate(s) {
  const d = parseD(s); if (!d) return '—';
  return `${d.getDate()} ${THMONTH[d.getMonth()]} ${(d.getFullYear() + 543) % 100}`;
}
function fmtDateFull(s) {
  const d = parseD(s); if (!d) return '—';
  return `${d.getDate()} ${THMONTH[d.getMonth()]} ${d.getFullYear() + 543}`;
}
function daysUntil(s) {
  const d = parseD(s); if (!d) return null;
  return Math.round((d - TODAY) / 86400000);
}
/* status: ok | warn | danger | none */
function statusOf(inst) {
  if (!inst.exp) return { key: 'none', label: 'ไม่มีข้อมูลสอบเทียบ', en: 'No data', days: null };
  const dd = daysUntil(inst.exp);
  if (dd < 0)         return { key: 'danger', label: 'เกินกำหนด', en: 'Overdue', days: dd };
  if (dd <= NEAR_DAYS) return { key: 'warn', label: 'ใกล้ครบกำหนด', en: 'Due soon', days: dd };
  return { key: 'ok', label: 'ปกติ', en: 'Valid', days: dd };
}
const STATUS_ORDER = { danger: 0, warn: 1, none: 2, ok: 3 };

/* ---------- small components ---------- */
function Pill({ status, showDays }) {
  const s = typeof status === 'string' ? { key: status } : status;
  const labels = { ok: 'ปกติ', warn: 'ใกล้ครบกำหนด', danger: 'เกินกำหนด', none: 'ไม่มีข้อมูล' };
  let txt = s.label || labels[s.key];
  if (showDays && s.days != null) {
    if (s.days < 0) txt = `เกิน ${Math.abs(s.days)} วัน`;
    else if (s.key !== 'ok') txt = `อีก ${s.days} วัน`;
  }
  return (
    <span className={`pill ${s.key}`}>
      <span className="dot"></span>{txt}
    </span>
  );
}

function DeptDot({ dept }) {
  return <span className="dept-dot" style={{ background: deptColor(dept) }}></span>;
}

function DeptBadge({ dept, onClick }) {
  const m = DEPT_META[dept] || {};
  return (
    <span onClick={onClick} style={{
      display: 'inline-flex', alignItems: 'center', gap: 7, cursor: onClick ? 'pointer' : 'default',
    }}>
      <DeptDot dept={dept} />
      <span style={{ fontSize: 13, color: 'var(--ink-2)', fontWeight: 500 }}>{m.short || dept}</span>
    </span>
  );
}

/* circular progress ring */
function Ring({ pct, size = 64, stroke = 7, color, track, label, sub }) {
  const r = (size - stroke) / 2;
  const c = 2 * Math.PI * r;
  const off = c * (1 - pct / 100);
  return (
    <div style={{ position: 'relative', width: size, height: size, flex: 'none' }}>
      <svg width={size} height={size} style={{ transform: 'rotate(-90deg)' }}>
        <circle cx={size/2} cy={size/2} r={r} fill="none" stroke={track || 'var(--surface-2)'} strokeWidth={stroke} />
        <circle cx={size/2} cy={size/2} r={r} fill="none" stroke={color || 'var(--accent)'} strokeWidth={stroke}
                strokeDasharray={c} strokeDashoffset={off} strokeLinecap="round"
                style={{ transition: 'stroke-dashoffset .9s cubic-bezier(.2,.7,.2,1)' }} />
      </svg>
      {label != null && (
        <div style={{ position: 'absolute', inset: 0, display: 'grid', placeItems: 'center', textAlign: 'center', lineHeight: 1 }}>
          <div>
            <div className="figure" style={{ fontSize: size * 0.28 }}>{label}</div>
            {sub && <div style={{ fontSize: 9.5, color: 'var(--ink-3)', marginTop: 2 }}>{sub}</div>}
          </div>
        </div>
      )}
    </div>
  );
}

/* copy-to-clipboard helper */
function CopyBtn({ text, size = 15 }) {
  const [done, setDone] = useState(false);
  return (
    <button className="btn-icon btn-ghost" title="คัดลอก" style={{ color: done ? 'var(--ok)' : 'var(--ink-3)' }}
      onClick={(e) => { e.stopPropagation(); navigator.clipboard?.writeText(text); setDone(true); setTimeout(() => setDone(false), 1200); }}>
      {done ? <I.check size={size} /> : <I.copy size={size} />}
    </button>
  );
}

/* deterministic QR-like matrix (decorative fallback, seeded by code).
   Used only if the real QR library failed to load (offline/CDN blocked). */
function QRDecorative({ text, size = 132, n = 21 }) {
  const cells = useMemo(() => {
    let h = 2166136261;
    const s = String(text == null ? '' : text);
    for (let i = 0; i < s.length; i++) { h ^= s.charCodeAt(i); h = Math.imul(h, 16777619); }
    const rng = () => { h ^= h << 13; h ^= h >>> 17; h ^= h << 5; return ((h >>> 0) % 1000) / 1000; };
    const grid = [];
    for (let y = 0; y < n; y++) for (let x = 0; x < n; x++) {
      const finder = (gx, gy) => (gx < 7 && gy < 7) || (gx >= n - 7 && gy < 7) || (gx < 7 && gy >= n - 7);
      if (finder(x, y)) {
        const lx = x >= n - 7 ? x - (n - 7) : x, ly = y >= n - 7 ? y - (n - 7) : y;
        const on = lx === 0 || lx === 6 || ly === 0 || ly === 6 || (lx >= 2 && lx <= 4 && ly >= 2 && ly <= 4);
        if (on) grid.push([x, y]);
      } else if (rng() > 0.52) grid.push([x, y]);
    }
    return grid;
  }, [text, n]);
  const u = size / n;
  return (
    <svg width={size} height={size} viewBox={`0 0 ${size} ${size}`} style={{ display: 'block' }}>
      <rect width={size} height={size} fill="#fff" rx={6} />
      {cells.map(([x, y], i) => <rect key={i} x={x*u+u*0.08} y={y*u+u*0.08} width={u*0.84} height={u*0.84} rx={u*0.18} fill="#1a1714" />)}
    </svg>
  );
}

/* real, scannable QR code (ISO/IEC 18004) rendered in the same style.
   Encodes `text` (the instrument code) — scanning returns that string. */
function QRMatrix({ text, size = 132, n = 21 }) {
  const qr = useMemo(() => {
    if (typeof window.qrcode !== 'function') return null;
    try {
      const q = window.qrcode(0, 'M');           // type 0 = auto-fit, M = 15% error correction
      q.addData(String(text == null ? '' : text));
      q.make();
      const count = q.getModuleCount();
      const grid = [];
      for (let r = 0; r < count; r++) for (let c = 0; c < count; c++) { if (q.isDark(r, c)) grid.push([c, r]); }
      return { count, grid };
    } catch (e) { return null; }
  }, [text]);

  // fallback to the decorative matrix if the QR lib is unavailable
  if (!qr) return <QRDecorative text={text} size={size} n={n} />;

  const margin = 2;                              // quiet zone (modules) for reliable scanning
  const total = qr.count + margin * 2;
  const u = size / total;
  return (
    <svg width={size} height={size} viewBox={`0 0 ${size} ${size}`} style={{ display: 'block' }}>
      <rect width={size} height={size} fill="#fff" rx={6} />
      {qr.grid.map(([x, y], i) => <rect key={i} x={(x+margin)*u+u*0.08} y={(y+margin)*u+u*0.08} width={u*0.84} height={u*0.84} rx={u*0.18} fill="#1a1714" />)}
    </svg>
  );
}

/* deep-link URL for an instrument — scanning its QR opens the detail card.
   Built from the current host so it works wherever the app is deployed. */
function instrumentURL(code) {
  const loc = window.location;
  return loc.origin + loc.pathname + '#/detail/' + encodeURIComponent(code);
}

function EmptyState({ icon, title, sub }) {
  const Ico = icon || I.beaker;
  return (
    <div style={{ textAlign: 'center', padding: '52px 20px', color: 'var(--ink-3)' }}>
      <div style={{ width: 52, height: 52, borderRadius: 14, background: 'var(--surface-2)', display: 'grid', placeItems: 'center', margin: '0 auto 14px', color: 'var(--ink-4)' }}>
        <Ico size={24} />
      </div>
      <div style={{ fontWeight: 600, color: 'var(--ink-2)', fontSize: 15 }}>{title}</div>
      {sub && <div style={{ fontSize: 13, marginTop: 4 }}>{sub}</div>}
    </div>
  );
}

Object.assign(window, {
  TODAY, NEAR_DAYS, Icon, I, DEPT_META, DEPT_OWNER, deptLoc, deptColor, deptSoft,
  THMONTH, parseD, fmtDate, fmtDateFull, daysUntil, statusOf, STATUS_ORDER,
  Pill, DeptDot, DeptBadge, Ring, CopyBtn, QRMatrix, instrumentURL, EmptyState,
  useState, useEffect, useRef, useMemo, useCallback,
});
