/* ===== utils.jsx — date, fmt, alarms, ids ===== */
(function () {
  const TODAY = new Date('2026-05-28');

  // Date parse/format
  const pad2 = (n) => String(n).padStart(2, '0');
  const toISO = (d) => d ? `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}` : '';
  const parseISO = (s) => s ? new Date(s + 'T00:00:00') : null;
  const daysBetween = (a, b) => Math.floor((a - b) / 86400000);
  const addDays = (d, n) => { const x = new Date(d); x.setDate(x.getDate() + n); return x; };
  const today = () => new Date(TODAY);

  // Smart date completion: "0901" → "2025-09-01" relative to today's year
  function smartDate(input) {
    if (!input) return '';
    const raw = input.replace(/[^0-9]/g, '');
    const y = today().getFullYear();
    if (raw.length === 4) return `${y}-${raw.slice(0, 2)}-${raw.slice(2, 4)}`;
    if (raw.length === 6) return `20${raw.slice(0, 2)}-${raw.slice(2, 4)}-${raw.slice(4, 6)}`;
    if (raw.length === 8) return `${raw.slice(0, 4)}-${raw.slice(4, 6)}-${raw.slice(6, 8)}`;
    return input;
  }

  const fmtDate = (s) => {
    if (!s) return '—';
    const d = parseISO(s);
    return `${d.getFullYear()}/${pad2(d.getMonth() + 1)}/${pad2(d.getDate())}`;
  };
  const fmtDateShort = (s) => {
    if (!s) return '—';
    const d = parseISO(s);
    return `${pad2(d.getMonth() + 1)}/${pad2(d.getDate())}`;
  };
  const fmtRel = (s) => {
    if (!s) return '';
    const d = parseISO(s);
    const diff = daysBetween(today(), d);
    if (diff === 0) return '今天';
    if (diff === 1) return '昨天';
    if (diff === -1) return '明天';
    if (diff > 0 && diff < 30) return `${diff} 天前`;
    if (diff > 0 && diff < 365) return `${Math.floor(diff / 30)} 个月前`;
    if (diff < 0 && diff > -30) return `${-diff} 天后`;
    if (diff < 0) return `${Math.floor(-diff / 30)} 个月后`;
    return `${Math.floor(diff / 365)} 年前`;
  };

  // ===== Alarm logic =====
  // Reagent alarm types (severity desc):
  //   EXPIRED       🔴 red-600    expiry < today
  //   EXPIRING      🟠 orange-500 expiry within 60d
  //   LOW_STOCK     🟡 amber-500  on_hand <= threshold
  //   OUTFLOW_OVER  🔵 sky-500    outflow not returned & due_at < today
  //   STALE         ⚫ zinc-700   no movement 365d
  // Device alarm types:
  //   DEVICE_FAULT  🔧 rose-500   status = FAULT
  //   REPAIR_OVER   🟣 violet-500 sent_to_repair > 60d

  const ALARM_META = {
    EXPIRED:      { emoji: '🔴', label: '已过期',     color: 'red',     tw: 'bg-red-50 text-red-700 ring-red-200',           dot: 'bg-red-600',     accent: '#dc2626' },
    EXPIRING:     { emoji: '🟠', label: '即将过期',   color: 'orange',  tw: 'bg-orange-50 text-orange-700 ring-orange-200', dot: 'bg-orange-500',  accent: '#f97316' },
    LOW_STOCK:    { emoji: '🟡', label: '低库存',     color: 'amber',   tw: 'bg-amber-50 text-amber-800 ring-amber-200',     dot: 'bg-amber-500',   accent: '#f59e0b' },
    OUTFLOW_OVER: { emoji: '🔵', label: '取货超期',   color: 'sky',     tw: 'bg-sky-50 text-sky-700 ring-sky-200',           dot: 'bg-sky-500',     accent: '#0ea5e9' },
    STALE:        { emoji: '⚫', label: '滞库',       color: 'zinc',    tw: 'bg-zinc-100 text-zinc-700 ring-zinc-300',       dot: 'bg-zinc-700',    accent: '#3f3f46' },
    DEVICE_FAULT: { emoji: '🔧', label: '设备故障',   color: 'rose',    tw: 'bg-rose-50 text-rose-700 ring-rose-200',         dot: 'bg-rose-500',    accent: '#f43f5e' },
    REPAIR_OVER:  { emoji: '🟣', label: '修复超期',   color: 'violet',  tw: 'bg-violet-50 text-violet-700 ring-violet-200',  dot: 'bg-violet-500',  accent: '#8b5cf6' },
  };

  function reagentAlarms(batch, product) {
    const out = [];
    if (!batch) return out;
    const t = today();
    const exp = parseISO(batch.expiry);
    if (exp) {
      const d = daysBetween(exp, t); // positive = future
      if (d < 0) out.push({ type: 'EXPIRED', detail: `${-d} 天前过期` });
      else if (d <= 60) out.push({ type: 'EXPIRING', detail: `${d} 天后过期` });
    }
    const threshold = (product && product.low_threshold) ?? 2;
    if (batch.on_hand_boxes !== undefined && batch.on_hand_boxes <= threshold) {
      out.push({ type: 'LOW_STOCK', detail: `剩 ${batch.on_hand_boxes} 盒 / 阈值 ${threshold}` });
    }
    if (batch.last_movement) {
      const lm = parseISO(batch.last_movement);
      if (daysBetween(t, lm) > 365 && batch.on_hand_boxes > 0) {
        out.push({ type: 'STALE', detail: `${Math.floor(daysBetween(t, lm) / 30)} 个月无动销` });
      }
    }
    return out;
  }

  function outflowAlarms(outflow) {
    const out = [];
    if (!outflow.due_at || outflow.returned_at) return out;
    const due = parseISO(outflow.due_at);
    const d = daysBetween(today(), due);
    if (d > 0) out.push({ type: 'OUTFLOW_OVER', detail: `逾期 ${d} 天未还` });
    return out;
  }

  function deviceAlarms(unit) {
    const out = [];
    if (unit.status === 'FAULT') {
      out.push({ type: 'DEVICE_FAULT', detail: unit.fault_note || '故障未处理' });
      if (unit.sent_to_repair_at) {
        const d = daysBetween(today(), parseISO(unit.sent_to_repair_at));
        if (d > 60) out.push({ type: 'REPAIR_OVER', detail: `送修 ${d} 天未归` });
      }
    }
    return out;
  }

  function severity(type) {
    return { EXPIRED: 5, DEVICE_FAULT: 4, OUTFLOW_OVER: 4, EXPIRING: 3, REPAIR_OVER: 3, LOW_STOCK: 2, STALE: 1 }[type] || 0;
  }

  // Formatters
  const fmtQty = (b, atoms) => {
    if (!atoms) return `${b} 盒`;
    return `${b} 盒 + ${atoms} 个 test`;
  };
  const fmtCurrency = (n, cur = 'EUR') => {
    if (n == null) return '—';
    return new Intl.NumberFormat('it-IT', { style: 'currency', currency: cur, maximumFractionDigits: 0 }).format(n);
  };
  const cls = (...xs) => xs.filter(Boolean).join(' ');

  // ID gen
  let _id = 1000;
  const nextId = (p) => `${p}-${++_id}`;

  // Group-by
  function groupBy(arr, fn) {
    const m = new Map();
    for (const x of arr) {
      const k = fn(x);
      if (!m.has(k)) m.set(k, []);
      m.get(k).push(x);
    }
    return m;
  }

  Object.assign(window, {
    TODAY, today, toISO, parseISO, daysBetween, addDays, smartDate,
    fmtDate, fmtDateShort, fmtRel, fmtQty, fmtCurrency, cls, nextId, groupBy,
    ALARM_META, reagentAlarms, outflowAlarms, deviceAlarms, severity,
  });
})();
