// cccd.jsx — Quét mã QR trên thẻ CCCD gắn chip → điền sẵn hồ sơ nhân sự.
// Nạp SAU ui.jsx, TRƯỚC hrm-profile.jsx / hrm.jsx.
//
// MÃ QR CCCD chứa 7 phần ngăn bằng '|':
//   012345678901|123456789|Nguyễn Văn A|01011990|Nam|Số 1, P.X, Q.Y, TP.Z|01012021
//     CCCD         CMND cũ   họ tên      dd MM yyyy  GT     thường trú     ngày cấp
// Chỉ có bấy nhiêu — KHÔNG có quê quán/dân tộc/hạn thẻ.
//
// ⚠️ Thẻ CĂN CƯỚC mẫu mới (từ 7/2024) CÓ THỂ khác định dạng → bộ đọc này PHÒNG THỦ:
// sai dạng thì trả null để UI hiện chuỗi thô cho HR tự nhập, TUYỆT ĐỐI không đoán bừa
// (điền sai CCCD/ngày sinh vào hồ sơ lao động nguy hiểm hơn là bắt gõ tay).
//
// KHÔNG dùng BarcodeDetector: đã kiểm — Chrome trên WINDOWS không có API này (chỉ Android/
// ChromeOS/macOS), mà máy HR chủ yếu là Windows → sẽ hỏng đúng chỗ cần dùng. Dùng jsQR,
// nạp LƯỜI khi mở hộp quét (257KB, không nằm trong shell). Giải mã CHẠY TRONG MÁY, ảnh
// không gửi đi đâu.

const CCCD_JSQR_URL = 'https://cdn.jsdelivr.net/npm/jsqr@1.4.0/dist/jsQR.js';
let _cccdJsqrP = null;
function cccdLoadJsQR(){
  if (window.jsQR) return Promise.resolve(window.jsQR);
  if (_cccdJsqrP) return _cccdJsqrP;
  _cccdJsqrP = new Promise((res, rej) => {
    const s = document.createElement('script');
    s.src = CCCD_JSQR_URL;
    s.onload = () => res(window.jsQR);
    s.onerror = () => { _cccdJsqrP = null; rej(new Error('jsqr-load-failed')); };
    document.head.appendChild(s);
  });
  return _cccdJsqrP;
}

// 'ddMMyyyy' → 'YYYY-MM-DD'; sai định dạng/ngày vô lý → '' (không đoán).
function cccdDate(v){
  const s = String(v || '').trim();
  if (!/^\d{8}$/.test(s)) return '';
  const d = +s.slice(0, 2), m = +s.slice(2, 4), y = +s.slice(4);
  if (m < 1 || m > 12 || d < 1 || d > 31 || y < 1900 || y > 2100) return '';
  return s.slice(4) + '-' + s.slice(2, 4) + '-' + s.slice(0, 2);
}
// Tên trên thẻ IN HOA ("NGUYỄN VĂN A") — hồ sơ trong hệ thống viết hoa đầu từ → đổi cho đồng bộ.
// HR vẫn sửa được ở form (hiện sẵn trong ô xem trước trước khi bấm dùng).
function cccdTitleCase(s){
  return String(s || '').trim().toLowerCase().split(/\s+/)
    .map(w => w ? w[0].toLocaleUpperCase('vi') + w.slice(1) : w).join(' ');
}
// Trả { idNumber, oldId, fullName, birthDate, gender, address, issueDate } hoặc null nếu KHÔNG khớp dạng.
function parseCccdQr(raw){
  const s = String(raw || '').replace(/ /g, '').trim().normalize('NFC');   // chuẩn hóa: máy quét/nguồn khác có thể trả NFD
  if (!s || s.indexOf('|') < 0) return null;
  const p = s.split('|').map(x => x.trim());
  if (p.length < 6) return null;
  const idNumber = p[0], fullName = p[2], birthDate = cccdDate(p[3]), address = p[5];
  if (!/^\d{9,12}$/.test(idNumber)) return null;      // CCCD 12 số (CMND cũ 9 số)
  if (!fullName || !birthDate) return null;           // thiếu 1 trong 2 → coi như không đọc được
  const sex = String(p[4] || '').normalize('NFC').toLowerCase();
  const gender = sex.startsWith('nam') ? 'male' : sex.startsWith('nữ') ? 'female' : '';
  return { idNumber, oldId:p[1] || '', fullName:cccdTitleCase(fullName), birthDate, gender,
    address:address || '', issueDate:cccdDate(p[6] || ''), raw:s };
}

// Giải mã QR từ ẢNH. Ảnh điện thoại 12MP quét chậm → thử bản thu nhỏ (cạnh dài ≤2000px, QR
// vẫn đủ nét vì chiếm ~18% chiều ngang thẻ) rồi mới thử ảnh gốc nếu trượt.
async function cccdDecodeImage(file){
  const jsQR = await cccdLoadJsQR();
  const url = URL.createObjectURL(file);
  try {
    const img = new Image();
    img.src = url;
    await (img.decode ? img.decode() : new Promise((r, j) => { img.onload = r; img.onerror = j; }));
    const scan = (maxSide) => {
      const scale = maxSide && Math.max(img.width, img.height) > maxSide ? maxSide / Math.max(img.width, img.height) : 1;
      const w = Math.round(img.width * scale), h = Math.round(img.height * scale);
      const c = document.createElement('canvas'); c.width = w; c.height = h;
      const ctx = c.getContext('2d', { willReadFrequently:true });
      ctx.drawImage(img, 0, 0, w, h);
      const r = jsQR(ctx.getImageData(0, 0, w, h).data, w, h, { inversionAttempts:'attemptBoth' });
      return r && r.data ? r.data : null;
    };
    return scan(2000) || (Math.max(img.width, img.height) > 2000 ? scan(0) : null);
  } finally { URL.revokeObjectURL(url); }
}

/* ---------------- Quét bằng CAMERA (live) ----------------
   QR trên CCCD chỉ ~1,5cm → chụp 1 ảnh là "ăn may": mờ/lệch nét là trượt. Camera trực tiếp quét
   LIÊN TỤC nhiều khung/giây nên khả năng bắt được cao hơn hẳn. 3 thứ quyết định thành/bại:
   1) XIN ĐỘ PHÂN GIẢI CAO (ideal 1920/2560) — mã nhỏ mà khung 640×480 thì không đủ điểm ảnh.
   2) CẮT GIỮA rồi quét ở TỶ LỆ 1:1 — thu nhỏ cả khung là xoá mất mã; chỉ quét vùng trong khung ngắm.
   3) ZOOM quang/số nếu máy hỗ trợ → mã to lên gấp đôi, đây là thứ cứu ca CCCD nhiều nhất. */
const CCCD_SCAN_MODES = {
  auto:   { vi:'Tự động (khuyến nghị)', en:'Auto' },
  camera: { vi:'Ưu tiên camera',        en:'Camera first' },
  image:  { vi:'Chỉ ảnh chụp/tệp',      en:'Image only' },
  text:   { vi:'Chỉ ô nhập chuỗi (máy quét cầm tay)', en:'Text only (handheld scanner)' },
};
function cccdScanMode(st){ const m = (st || {}).cccdScanMode; return CCCD_SCAN_MODES[m] ? m : 'auto'; }
// Có camera sau (điện thoại/tablet) hay không → chế độ 'auto' dựa vào đây để chọn mặc định.
async function cccdHasCamera(){
  try {
    if (!(navigator.mediaDevices && navigator.mediaDevices.enumerateDevices)) return false;
    const ds = await navigator.mediaDevices.enumerateDevices();
    return ds.some(d => d.kind === 'videoinput');
  } catch (e) { return false; }
}

function CccdCameraScan({ onText, onError }){
  const { lang } = useLang();
  const L = (vi, en) => (lang === 'vi' ? vi : en);
  const videoRef = React.useRef(), wrapRef = React.useRef();
  const stateRef = React.useRef({ stop:false, stream:null });
  const [torch, setTorch] = React.useState(null);      // null = máy không có đèn
  const [zoom, setZoom] = React.useState(null);        // {min,max,step,val} nếu hỗ trợ
  const [hint, setHint] = React.useState('');

  React.useEffect(() => {
    const S = stateRef.current;
    let raf = 0, last = 0;
    (async () => {
      let jsQR;
      try { jsQR = await cccdLoadJsQR(); }
      catch (e) { onError(L('Không tải được bộ giải mã — cần mạng cho lần đầu.', 'Decoder failed to load.')); return; }
      let stream;
      try {
        stream = await navigator.mediaDevices.getUserMedia({
          video: { facingMode:{ ideal:'environment' }, width:{ ideal:1920 }, height:{ ideal:1080 } }, audio:false });
      } catch (e) {
        onError(e && e.name === 'NotAllowedError'
          ? L('Bạn đã từ chối quyền camera — mở lại quyền trong trình duyệt, hoặc dùng ảnh chụp.', 'Camera permission denied.')
          : L('Không mở được camera — dùng ảnh chụp thay thế.', 'Cannot open camera.'));
        return;
      }
      if (S.stop) { stream.getTracks().forEach(t => t.stop()); return; }   // đã đóng hộp trước khi cấp quyền
      S.stream = stream;
      const v = videoRef.current;
      if (!v) { stream.getTracks().forEach(t => t.stop()); return; }
      v.srcObject = stream; v.setAttribute('playsinline', '');
      try { await v.play(); } catch (e) {}
      const track = stream.getVideoTracks()[0];
      const caps = track.getCapabilities ? (track.getCapabilities() || {}) : {};
      if (caps.torch) setTorch(false);
      if (caps.zoom) {   // zoom sẵn → kéo lên ~2× cho mã to hơn (đây là thứ cứu ca QR nhỏ)
        const start = Math.min(caps.zoom.max, Math.max(caps.zoom.min, 2));
        try { await track.applyConstraints({ advanced:[{ zoom:start }] }); } catch (e) {}
        setZoom({ min:caps.zoom.min, max:caps.zoom.max, step:caps.zoom.step || 0.1, val:start });
      }
      try { await track.applyConstraints({ advanced:[{ focusMode:'continuous' }] }); } catch (e) {}
      const c = document.createElement('canvas');
      const ctx = c.getContext('2d', { willReadFrequently:true });
      const tick = (ts) => {
        if (S.stop) return;
        raf = requestAnimationFrame(tick);
        if (ts - last < 120) return;              // ~8 khung/giây là đủ, đỡ nóng máy
        last = ts;
        const vw = v.videoWidth, vh = v.videoHeight;
        if (!vw || !vh) return;
        // CẮT GIỮA đúng vùng khung ngắm, quét ở tỷ lệ 1:1 (KHÔNG thu nhỏ → giữ nguyên điểm ảnh của mã)
        const side = Math.min(vw, vh) * 0.7, sx = (vw - side) / 2, sy = (vh - side) / 2;
        const n = Math.min(Math.round(side), 1000);
        if (c.width !== n) { c.width = n; c.height = n; }
        ctx.drawImage(v, sx, sy, side, side, 0, 0, n, n);
        const r = jsQR(ctx.getImageData(0, 0, n, n).data, n, n, { inversionAttempts:'dontInvert' });
        if (r && r.data) { S.stop = true; onText(r.data); }
      };
      raf = requestAnimationFrame(tick);
      setHint(L('Đưa mã QR (góc phải mặt trước thẻ) vào khung — lại gần ~10cm', 'Fit the QR inside the box — get close'));
    })();
    // BẮT BUỘC dọn: không tắt track thì đèn camera vẫn sáng sau khi đóng hộp
    return () => { S.stop = true; cancelAnimationFrame(raf); if (S.stream) S.stream.getTracks().forEach(t => t.stop()); };
  }, []);

  const setZoomVal = async (val) => {
    const S = stateRef.current; if (!S.stream) return;
    const track = S.stream.getVideoTracks()[0];
    try { await track.applyConstraints({ advanced:[{ zoom:val }] }); setZoom(z => ({ ...z, val })); } catch (e) {}
  };
  const toggleTorch = async () => {
    const S = stateRef.current; if (!S.stream) return;
    const track = S.stream.getVideoTracks()[0];
    try { await track.applyConstraints({ advanced:[{ torch:!torch }] }); setTorch(t => !t); } catch (e) {}
  };
  return (
    <div className="col g8">
      <div ref={wrapRef} style={{ position:'relative', background:'#000', borderRadius:'var(--r-md)', overflow:'hidden' }}>
        <video ref={videoRef} muted playsInline style={{ width:'100%', maxHeight:300, objectFit:'cover', display:'block' }} />
        {/* khung ngắm = ĐÚNG vùng đang được quét (70% cạnh ngắn) → user biết phải đưa mã vào đâu */}
        <div style={{ position:'absolute', inset:0, display:'flex', alignItems:'center', justifyContent:'center', pointerEvents:'none' }}>
          <div style={{ width:'55%', aspectRatio:'1/1', border:'2px solid var(--primary)', borderRadius:8, boxShadow:'0 0 0 9999px rgba(0,0,0,.35)' }}></div>
        </div>
      </div>
      {hint && <div className="subtle" style={{ fontSize:'var(--fs-sm)' }}><i className="bi bi-info-circle" style={{ marginRight:5 }}></i>{hint}</div>}
      <div className="flex items-center g8 wrap">
        {torch !== null && <button className="btn btn-sm" onClick={toggleTorch}><i className={'bi ' + (torch ? 'bi-lightbulb-fill' : 'bi-lightbulb')}></i>{L('Đèn', 'Torch')}</button>}
        {zoom && (
          <label className="flex items-center g8" style={{ flex:'1 1 160px', fontSize:'var(--fs-sm)' }}>
            <i className="bi bi-zoom-in subtle"></i>
            <input type="range" style={{ flex:1 }} min={zoom.min} max={zoom.max} step={zoom.step} value={zoom.val}
              onChange={ev => setZoomVal(+ev.target.value)} />
          </label>
        )}
      </div>
    </div>
  );
}

/* Hộp quét CCCD — onDone(info) trả về object đã parse. Dùng ở form nhân viên + danh sách nhân sự. */
function CccdScanModal({ onClose, onDone }){
  const p = usePortal();
  const { lang } = useLang();
  const L = (vi, en) => (lang === 'vi' ? vi : en);
  const toast = useToast();
  const [busy, setBusy] = React.useState(false);
  const [info, setInfo] = React.useState(null);
  const [raw, setRaw] = React.useState('');       // chuỗi thô: máy quét cầm tay gõ vào đây, hoặc dán tay
  const [failed, setFailed] = React.useState('');
  const [cam, setCam] = React.useState(false);
  const fileRef = React.useRef();
  const mode = cccdScanMode(hrmSettings(p.appSettings));
  // 'auto': máy có camera (điện thoại/tablet) → bật camera luôn vì QR trên CCCD quá nhỏ để chụp 1 phát;
  // máy bàn không camera → về ảnh/tệp. Chế độ ép cứng thì theo đúng lựa chọn ở Thiết lập.
  React.useEffect(() => {
    let ok = true;
    if (mode === 'camera') { setCam(true); return; }
    if (mode === 'auto') cccdHasCamera().then(has => { if (ok && has) setCam(true); });
    return () => { ok = false; };
  }, [mode]);

  const apply = (text) => {
    const parsed = parseCccdQr(text);
    if (parsed) { setInfo(parsed); setFailed(''); }
    else { setInfo(null); setFailed(text || ''); }
  };
  const onPick = async (ev) => {
    const file = (ev.target.files || [])[0]; ev.target.value = '';
    if (!file) return;
    setBusy(true); setInfo(null); setFailed('');
    try {
      const text = await cccdDecodeImage(file);
      if (!text) { toast(L('Không tìm thấy mã QR trong ảnh — chụp gần và rõ mặt trước thẻ.', 'No QR found in image.'), 'error'); }
      else { setRaw(text); apply(text); }
    } catch (e) {
      toast(L('Không tải được bộ giải mã (cần mạng cho lần đầu). Bạn có thể dán chuỗi quét được.', 'Decoder failed to load — you can paste the scanned text.'), 'error');
    }
    setBusy(false);
  };
  const Row = ({ k, v }) => (
    <div className="flex justify-between g10" style={{ padding:'3px 0', fontSize:'var(--fs-base)' }}>
      <span className="muted" style={{ flex:'none' }}>{k}</span><span style={{ fontWeight:500, textAlign:'right' }}>{v || '—'}</span>
    </div>
  );
  return (
    <Modal title={L('Quét mã QR trên thẻ CCCD', 'Scan ID card QR')} width={520} onClose={onClose} staticBackdrop
      footer={<button className="btn" onClick={onClose}>{L('Hủy', 'Cancel')}</button>}>
      <div className="col g16">
        {cam && !info && (
          <CccdCameraScan
            onText={(text) => { setCam(false); setRaw(text); apply(text); }}
            onError={(msg) => { setCam(false); toast(msg, 'error'); }} />
        )}
        {mode !== 'text' && !cam && (
          <div className="flex items-center g8 wrap">
            {mode !== 'image' && (
              <button className="btn btn-primary" onClick={() => { setInfo(null); setFailed(''); setCam(true); }}>
                <i className="bi bi-camera-video"></i>{L('Quét bằng camera', 'Scan with camera')}</button>
            )}
            {/* capture="environment": trên điện thoại bấm là mở THẲNG camera chụp; máy tính mở hộp chọn ảnh */}
            <button className={'btn' + (mode === 'image' ? ' btn-primary' : '')} disabled={busy} onClick={() => fileRef.current && fileRef.current.click()}>
              <i className={'bi ' + (busy ? 'bi-arrow-repeat' : 'bi-image')}></i>{busy ? L('Đang đọc…', 'Reading…') : L('Chụp / chọn ảnh thẻ', 'Photo / pick image')}</button>
            <span className="subtle" style={{ fontSize:'var(--fs-sm)' }}>{L('Mã QR ở góc phải MẶT TRƯỚC thẻ', 'QR is top-right on the front side')}</span>
            <input ref={fileRef} type="file" accept="image/*" capture="environment" style={{ display:'none' }} onChange={onPick} />
          </div>
        )}
        {cam && <button className="btn btn-sm" style={{ alignSelf:'flex-start' }} onClick={() => setCam(false)}><i className="bi bi-x-lg"></i>{L('Tắt camera', 'Stop camera')}</button>}
        <Field label={L('Hoặc chuỗi quét được (máy quét cầm tay / dán tay)', 'Or scanned text (handheld scanner / paste)')}>
          <input className="input mono" value={raw} autoFocus placeholder="012345678901|…|Nguyễn Văn A|01011990|Nam|…"
            onChange={ev => { setRaw(ev.target.value); apply(ev.target.value); }} />
        </Field>
        {info && (
          <div className="card" style={{ padding:'10px 12px', background:'var(--bg-subtle)' }}>
            <div className="flex items-center g8" style={{ marginBottom:6 }}>
              <i className="bi bi-check-circle-fill" style={{ color:'var(--success)' }}></i>
              <b style={{ fontSize:'var(--fs-base)' }}>{L('Đọc được — kiểm lại rồi bấm nút bên dưới', 'Parsed — review then apply below')}</b>
            </div>
            <Row k={L('Số CCCD', 'ID no.')} v={<span className="mono">{info.idNumber}</span>} />
            <Row k={L('Họ tên', 'Full name')} v={info.fullName} />
            <Row k={L('Ngày sinh', 'Birth date')} v={fmtDate(info.birthDate)} />
            <Row k={L('Giới tính', 'Gender')} v={info.gender === 'male' ? L('Nam', 'Male') : info.gender === 'female' ? L('Nữ', 'Female') : '—'} />
            <Row k={L('Nơi cư trú', 'Address')} v={info.address} />
            {info.issueDate && <Row k={L('Ngày cấp', 'Issued')} v={fmtDate(info.issueDate)} />}
            {/* Nút hành động NẰM NGAY TRONG THÂN, dưới thông tin (user yêu cầu): trên mobile footer
                modal từng bị che (z-index/scroll) → nút trong luồng nội dung LUÔN thấy ngay khi đọc được. */}
            <button className="btn btn-primary" style={{ width:'100%', marginTop:10, height:44 }}
              onClick={() => { onDone(info); onClose(); }}>
              <i className="bi bi-check-lg"></i>{L('Dùng thông tin này', 'Use this')}</button>
          </div>
        )}
        {!info && failed && (
          <div className="col g6" style={{ padding:'10px 12px', borderRadius:6, background:'color-mix(in srgb, var(--warning) 10%, transparent)' }}>
            <div className="flex items-center g8"><i className="bi bi-exclamation-triangle-fill" style={{ color:'var(--warning)' }}></i>
              <b style={{ fontSize:'var(--fs-base)' }}>{L('Đọc được mã nhưng KHÔNG đúng dạng CCCD', 'QR found but not an ID-card format')}</b></div>
            <div className="subtle" style={{ fontSize:'var(--fs-sm)' }}>{L('Thẻ căn cước mẫu mới có thể khác định dạng. Chuỗi thô bên dưới — bạn tự điền vào form cho chắc:', 'Raw text below — please fill the form manually:')}</div>
            <div className="mono" style={{ fontSize:'var(--fs-sm)', wordBreak:'break-all', background:'var(--bg)', padding:'6px 8px', borderRadius:4 }}>{failed}</div>
          </div>
        )}
      </div>
    </Modal>
  );
}

Object.assign(window, { parseCccdQr, cccdDecodeImage, cccdTitleCase, CccdScanModal, CccdCameraScan,
  CCCD_SCAN_MODES, cccdScanMode, cccdHasCamera });
