/* ============================================================
   meta.jsx — deterministic synthesized fields for the rich
   instrument card (location, owner, calibration cert, PM …).
   The source sheet only carries 2 dates per instrument, so the
   remaining ISO-15189 fields are generated deterministically
   from the instrument code for a realistic prototype.
   ============================================================ */
(function () {
  const OWNERS = ['ทนพ. ศิริพร วงศ์ดี', 'ทนพญ. กาญจนา ใจงาม', 'ทนพ. ธนวัฒน์ ผลบุญ', 'ทนพญ. พิมพ์ชนก สุขสันต์', 'ทนพ. อนุชา เรืองศรี', 'ทนพญ. ณัฐริกา พงษ์พันธ์'];
  const PROVIDERS = ['สถาบันมาตรวิทยาแห่งชาติ (NIMT)', 'บริษัทผู้แทนจำหน่าย (Authorized)', 'ศูนย์สอบเทียบเครื่องมือแพทย์ เขต 10', 'หน่วยวิศวกรรมการแพทย์ รพ.'];
  const FLOORS = ['ชั้น 1', 'ชั้น 2', 'ชั้น 3'];
  const VENDORS = ['บจก. ดีเคเอสเอช (ประเทศไทย)', 'บจก. กิบไทย', 'บจก. ไบโอจีนีเทค', 'บจก. เอ.เมดิคอล ซัพพลาย', 'บจก. เมดิทอป'];
  const CONDITIONS = ['ใหม่ (New)', 'ใหม่ (New)', 'ใหม่ (New)', 'ปรับปรุงสภาพ (Reconditioned)', 'รับโอน (Transferred-in)'];

  function hash(str) {
    let h = 2166136261;
    for (let i = 0; i < str.length; i++) { h ^= str.charCodeAt(i); h = Math.imul(h, 16777619); }
    return Math.abs(h);
  }
  function addDays(iso, n) {
    if (!iso) return null;
    const d = new Date(iso + 'T00:00:00'); d.setDate(d.getDate() + n);
    return d.toISOString().slice(0, 10);
  }

  function instrumentMeta(inst) {
    const h = hash(inst.code);
    const m = window.DEPT_META[inst.dept] || {};
    const s = window.statusOf(inst);

    // operational status — overdue instruments are more likely flagged
    let op;
    if (s.key === 'danger' && h % 3 === 0) op = { key: 'down', label: 'งดใช้ชั่วคราว' };
    else if (h % 19 === 0) op = { key: 'repair', label: 'รอซ่อมบำรุง' };
    else op = { key: 'up', label: 'ใช้งานได้' };

    const room = `${(m.abbr || 'LAB')}-${String((h % 8) + 1).padStart(2, '0')}`;
    const provider = PROVIDERS[h % PROVIDERS.length];

    // calibration interval from cal→exp, else assume 365d
    let interval = 365;
    if (inst.cal && inst.exp) {
      interval = Math.max(30, Math.round((new Date(inst.exp) - new Date(inst.cal)) / 86400000));
    }
    const pmCycleMonths = interval >= 330 ? 12 : 6;
    const cycleDays = pmCycleMonths * 30;
    // anchor PM schedule realistically around "today" so some are overdue,
    // some due soon, most upcoming — deterministic per instrument
    const todayIso = window.TODAY.toISOString().slice(0, 10);
    const pmOffset = (h % (cycleDays + 90)) - 75;
    const pmNext = inst.cal ? addDays(todayIso, pmOffset) : null;
    const pmLast = pmNext ? addDays(pmNext, -cycleDays) : null;
    const intake = inst.cal ? addDays(inst.cal, -interval * (1 + (h % 4))) : null;

    // ---- ISO 15189 master-list fields ----
    const isAnalyzer = /อัตโนมัติ|วิเคราะห์|analyz/i.test(`${inst.type || ''} ${inst.typeName || ''}`) || /AUT|BSC|PCR|SEQ|CBC/.test((inst.code || '').split('-')[2] || '');
    const warrantyEnd = intake ? addDays(intake, 365 * (1 + (h % 3)) + (h % 180)) : null;
    const commissionDate = intake ? addDays(intake, 3 + (h % 12)) : null;

    return {
      location: inst.location || (window.deptLoc ? window.deptLoc(inst.dept) : `งาน${m.th} (${m.short})`),
      room,
      owner: inst.owner || (window.DEPT_OWNER || {})[inst.dept] || OWNERS[h % OWNERS.length],
      op: inst.opKey ? { key: inst.opKey, label: { up: 'ใช้งานได้', down: 'งดใช้ชั่วคราว', repair: 'รอซ่อมบำรุง' }[inst.opKey] } : op,
      intake: inst.intake || intake,
      provider: inst.provider || provider,
      certNo: inst.certNo || `CAL-${(inst.exp ? new Date(inst.exp).getFullYear() : 2026) + 543}-${String(h % 9000 + 1000)}`,
      calResult: inst.cal ? 'ผ่านเกณฑ์ (Pass)' : null,
      uncertainty: `±${(0.2 + (h % 9) / 10).toFixed(1)}%`,
      interval,
      pmCycle: `ทุก ${pmCycleMonths} เดือน`,
      pmCycleMonths,
      pmLast: inst.pmLast || pmLast,
      pmNext: inst.pmNext || pmNext,
      qcFreq: ['ทุกวันทำการ', 'ทุกสัปดาห์', 'ทุกล็อตน้ำยา'][h % 3],
      // ISO 15189 master-list
      vendor: inst.vendor || VENDORS[h % VENDORS.length],
      vendorContact: inst.vendorContact || `0-2${String(100 + h % 900)}-${String(1000 + h % 9000)}`,
      commissionDate: inst.commissionDate || commissionDate,
      acceptance: inst.acceptance || (inst.cal ? 'ผ่านการตรวจรับ (Accepted)' : null),
      condition: inst.condition || CONDITIONS[h % CONDITIONS.length],
      firmware: inst.firmware || (isAnalyzer ? `v${1 + (h % 6)}.${h % 10}.${h % 24}` : '—'),
      warrantyEnd: inst.warrantyEnd || warrantyEnd,
      serviceContract: inst.serviceContract || (h % 3 === 0 ? 'มีสัญญาบำรุงรักษา (MA)' : 'บริการตามเรียก (On-call)'),
      manualRef: inst.manualRef || 'มี · จัดเก็บที่หน่วยงาน',
      lifecycle: inst.lifecycle || 'active',
      decommissionDate: inst.decommissionDate || null,
    };
  }

  window.instrumentMeta = instrumentMeta;

  /* ---- numeric calibration results vs acceptance criteria (ISO 15189 §6.5) ---- */
  function calibrationResults(inst) {
    if (!inst || !inst.cal) return null;
    const rnd = (seed) => (hash(inst.code + seed) % 1000) / 1000;
    const typ = (inst.code || '').split('-')[2] || '';
    const tname = `${inst.type || ''} ${inst.typeName || ''}`;

    const mk = (param, unit, setpoint, tol, biasFrac, seed) => {
      const bias = (rnd(seed) * 2 - 1) * tol * biasFrac;
      const measured = Math.round((setpoint + bias) * 100) / 100;
      return { param, unit, setpoint, tol, measured };
    };

    // category by unambiguous type-code abbreviation
    const TEMP = { REF: null, FRZ: null, INC: 37, PLT: 22, WAT: 37, HTR: 37, OVE: 170, CLV: 121, TMR: 'verify' };
    let rows;

    if (typ in TEMP) {
      if (typ === 'REF') {
        rows = [ mk('อุณหภูมิจุดควบคุม', '°C', 4, 2, 0.7, 'a'), mk('อุณหภูมิชั้นบน', '°C', 4, 2, 0.9, 'b'), mk('อุณหภูมิชั้นล่าง', '°C', 4, 2, 1.05, 'c') ];
      } else if (typ === 'FRZ') {
        const sp = /-80/.test(tname) ? -80 : /-40/.test(tname) ? -40 : /-30/.test(tname) ? -30 : -20;
        rows = [ mk('อุณหภูมิจุดควบคุม', '°C', sp, 5, 0.7, 'a'), mk('อุณหภูมิมุมซ้าย', '°C', sp, 5, 0.9, 'b'), mk('อุณหภูมิมุมขวา', '°C', sp, 5, 1.0, 'c') ];
      } else if (typ === 'TMR') {
        rows = [ mk('อ่านค่าที่จุด 0 °C', '°C', 0, 0.5, 0.6, 'a'), mk('อ่านค่าที่จุด 37 °C', '°C', 37, 0.5, 0.85, 'b'), mk('อ่านค่าที่จุด 100 °C', '°C', 100, 0.8, 0.9, 'c') ];
      } else {
        const sp = TEMP[typ];
        const tol = typ === 'CLV' || typ === 'OVE' ? 3 : typ === 'PLT' ? 2 : 1;
        rows = [ mk('อุณหภูมิจุดควบคุม', '°C', sp, tol, 0.65, 'a'), mk('อุณหภูมิตำแหน่งกลาง', '°C', sp, tol, 0.85, 'b'), mk('ความสม่ำเสมอ (uniformity)', '°C', sp, tol * 1.5, 0.9, 'c') ];
        if (typ === 'PLT') rows.push(mk('รอบการเขย่า', 'rpm', 60, 5, 0.8, 'd'));
      }
    } else if (typ === 'CEN' || typ === 'ACE' || typ === 'APH') {
      rows = [ mk('ความเร็วรอบ จุดที่ 1', 'rpm', 3000, 90, 0.7, 'a'), mk('ความเร็วรอบ จุดที่ 2', 'rpm', 5000, 150, 0.85, 'b'), mk('เวลา', 'วินาที', 300, 9, 0.7, 'c') ];
    } else if (typ === 'MXR' || typ === 'SHK') {
      rows = [ mk('ความเร็วรอบ', 'rpm', 1500, 75, 0.7, 'a'), mk('เวลา', 'วินาที', 60, 3, 0.75, 'b') ];
    } else if (typ === 'TIM') {
      rows = [ mk('จับเวลา 60 วินาที', 'วินาที', 60, 0.5, 0.6, 'a'), mk('จับเวลา 600 วินาที', 'วินาที', 600, 2, 0.85, 'b') ];
    } else if (typ === 'PIP' || typ === 'DIS') {
      rows = [ mk('ปริมาตรต่ำสุด', 'µL', 10, 0.3, 0.7, 'a'), mk('ปริมาตรกลาง', 'µL', 100, 1.5, 0.8, 'b'), mk('ปริมาตรสูงสุด', 'µL', 1000, 8, 0.9, 'c') ];
    } else if (typ === 'BAL' || typ === 'WGT' || typ === 'BWS') {
      rows = [ mk('มวลอ้างอิง 10 g', 'g', 10, 0.02, 0.6, 'a'), mk('มวลอ้างอิง 100 g', 'g', 100, 0.05, 0.8, 'b'), mk('มวลอ้างอิง 200 g', 'g', 200, 0.1, 0.9, 'c') ];
    } else if (typ === 'PCR' || typ === 'HTR') {
      rows = [ mk('อุณหภูมิ Denature', '°C', 95, 1, 0.7, 'a'), mk('อุณหภูมิ Anneal', '°C', 55, 1, 0.8, 'b'), mk('อุณหภูมิ Extension', '°C', 72, 1, 0.85, 'c') ];
    } else if (typ === 'MBB' || typ === 'TRB' || typ === 'ESR') {
      rows = [ mk('ค่าอ้างอิงระดับต่ำ', 'unit', 5, 0.5, 0.65, 'a'), mk('ค่าอ้างอิงระดับสูง', 'unit', 20, 1, 0.85, 'c') ];
    } else {
      rows = [ mk('ค่าอ้างอิงจุดที่ 1', 'unit', 50, 1.5, 0.65, 'a'), mk('ค่าอ้างอิงจุดที่ 2', 'unit', 100, 2.5, 0.85, 'c') ];
    }

    rows = rows.map((r, i) => {
      const dev = Math.round((r.measured - r.setpoint) * 100) / 100;
      const pass = Math.abs(dev) <= r.tol;
      const u = Math.round(r.tol * (0.18 + (hash(inst.code + 'u' + i) % 12) / 100) * 100) / 100;
      return { ...r, dev, pass, u };
    });
    const allPass = rows.every(r => r.pass);
    return { rows, allPass, method: 'เทียบกับมาตรฐานที่สอบกลับได้ (traceable standard)', overall: allPass ? 'ผ่านเกณฑ์ทุกจุด (Pass)' : 'มีจุดไม่ผ่านเกณฑ์ (Conditional)' };
  }
  window.calibrationResults = calibrationResults;
})();
