/* global React, ReactDOM, Icon, StepHeader, NumericKeypad, DniDisplay, totemLocale, totemMarket, totemOrgSlug, totemDateLocale */
// =====================================================================================================
// TÓTEM DE MEDICINA DEL TRABAJO (perfil ?profile=trabalho) — 14/10/2026
// CPF → tipo de exame → função/empresa → sinais vitais (PA, FC, peso, altura; Bluetooth o manual) → SENHA.
// El backend (POST /medical/public/totem/:slug/occupational/checkin) crea la ficha si no existe, deja el turno
// «en espera» para la recepción con todo cargado y devuelve la senha. Sin ?org= funciona en modo demostración.
// =====================================================================================================
(function () {
  const { useState: uS, useEffect: uE, useRef: uR } = React;

  const L = {
    pt: {
      step: 'Medicina do trabalho', cpfTitle: 'Informe seu CPF', cpfHint: 'Vamos localizar seu cadastro ou criar um novo.',
      cpfHintDemo: 'Modo demonstração: pode digitar qualquer CPF, nada é gravado.',
      continue: 'Continuar', back: 'Voltar', cancel: 'Cancelar',
      invalidCpf: 'CPF inválido. Confira os números.', checking: 'Localizando cadastro…',
      nameTitle: 'Seu nome completo', nameHint: 'Não encontramos o seu cadastro: digite o nome como está no documento.', namePh: 'Nome e sobrenome',
      hello: 'Olá,', isYou: 'É você?', yes: 'Sim, sou eu', no: 'Não, corrigir CPF',
      typeTitle: 'Qual é o tipo do exame?', typeHint: 'Escolha uma opção.',
      admissional: 'Admissional', admissionalSub: 'Vou começar em uma empresa',
      periodico: 'Periódico', periodicoSub: 'Exame de rotina da empresa',
      retorno_trabalho: 'Retorno ao trabalho', retorno_trabalhoSub: 'Voltando de afastamento',
      mudanca_funcao: 'Mudança de função', mudanca_funcaoSub: 'Vou mudar de cargo ou setor',
      demissional: 'Demissional', demissionalSub: 'Estou saindo da empresa',
      roleTitle: 'Sua função e empresa', rolePh: 'Função / cargo (ex.: auxiliar de produção)', companyPh: 'Empresa (opcional)',
      roleLabel: 'Função', companyLabel: 'Empresa',
      vitalsTitle: 'Medidas de saúde', vitalsHint: 'Use os aparelhos ao lado ou digite os valores. Pode pular o que não medir.',
      pressure: 'Pressão arterial (mmHg)', sys: 'Máxima (sistólica)', dia: 'Mínima (diastólica)', hr: 'Frequência cardíaca (bpm)',
      weight: 'Peso (kg)', height: 'Altura (cm)', bmi: 'IMC',
      btPressure: 'Medir pressão pelo aparelho', btScale: 'Pesar pela balança', btWaiting: 'Aguardando a medição no aparelho…',
      btUnsupported: 'Bluetooth indisponível neste navegador: digite os valores.', btFail: 'Não foi possível ler o aparelho. Digite os valores.',
      btDone: 'Medida recebida', skip: 'Pular medidas',
      sending: 'Gerando sua senha…', errGeneric: 'Não foi possível concluir. Procure a recepção.',
      errNotEnabled: 'Este totem ainda não está habilitado para esta clínica. Procure a recepção.',
      errInvalidVital: 'Algum valor das medidas parece incorreto. Revise e tente de novo.',
      ticketTitle: 'Tudo certo!', ticketBody: 'Aguarde ser chamado(a). Sua senha:', ticketPos: 'Posição na fila',
      ticketNew: 'Seu cadastro será completado na recepção.', print: 'Imprimir senha', finish: 'Concluir', finishIn: (n) => `Concluir (${n}s)`,
      printTitle: 'Senha — Medicina do trabalho',
    },
    es: {
      step: 'Medicina del trabajo', cpfTitle: 'Ingresá tu documento', cpfHint: 'Vamos a buscar tu ficha o crear una nueva.',
      cpfHintDemo: 'Modo demostración: podés ingresar cualquier documento, no se guarda nada.',
      continue: 'Continuar', back: 'Volver', cancel: 'Cancelar',
      invalidCpf: 'Documento inválido. Revisá los números.', checking: 'Buscando ficha…',
      nameTitle: 'Tu nombre completo', nameHint: 'No encontramos tu ficha: escribí tu nombre como figura en el documento.', namePh: 'Nombre y apellido',
      hello: 'Hola,', isYou: '¿Sos vos?', yes: 'Sí, soy yo', no: 'No, corregir documento',
      typeTitle: '¿Qué tipo de examen es?', typeHint: 'Elegí una opción.',
      admissional: 'Preocupacional', admissionalSub: 'Voy a empezar en una empresa',
      periodico: 'Periódico', periodicoSub: 'Examen de rutina de la empresa',
      retorno_trabalho: 'Reingreso', retorno_trabalhoSub: 'Vuelvo de una licencia',
      mudanca_funcao: 'Cambio de función', mudanca_funcaoSub: 'Voy a cambiar de puesto o sector',
      demissional: 'Egreso', demissionalSub: 'Dejo la empresa',
      roleTitle: 'Tu puesto y empresa', rolePh: 'Puesto / función', companyPh: 'Empresa (opcional)',
      roleLabel: 'Puesto', companyLabel: 'Empresa',
      vitalsTitle: 'Medidas de salud', vitalsHint: 'Usá los aparatos o escribí los valores. Podés saltear lo que no midas.',
      pressure: 'Presión arterial (mmHg)', sys: 'Máxima (sistólica)', dia: 'Mínima (diastólica)', hr: 'Frecuencia cardíaca (lpm)',
      weight: 'Peso (kg)', height: 'Altura (cm)', bmi: 'IMC',
      btPressure: 'Medir presión con el aparato', btScale: 'Pesar en la balanza', btWaiting: 'Esperando la medición en el aparato…',
      btUnsupported: 'Bluetooth no disponible en este navegador: escribí los valores.', btFail: 'No se pudo leer el aparato. Escribí los valores.',
      btDone: 'Medida recibida', skip: 'Saltear medidas',
      sending: 'Generando tu número…', errGeneric: 'No se pudo completar. Consultá en recepción.',
      errNotEnabled: 'Este tótem todavía no está habilitado para esta clínica. Consultá en recepción.',
      errInvalidVital: 'Algún valor de las medidas parece incorrecto. Revisalo e intentá de nuevo.',
      ticketTitle: '¡Listo!', ticketBody: 'Esperá a que te llamen. Tu número:', ticketPos: 'Posición en la fila',
      ticketNew: 'Tu ficha se completará en recepción.', print: 'Imprimir número', finish: 'Terminar', finishIn: (n) => `Terminar (${n}s)`,
      printTitle: 'Turno — Medicina del trabajo',
    },
  };
  const lang = () => (String(typeof totemLocale !== 'undefined' ? totemLocale : 'pt-BR').toLowerCase().indexOf('es') === 0 ? 'es' : 'pt');
  const T = () => L[lang()];

  const EXAMS = ['admissional', 'periodico', 'retorno_trabalho', 'mudanca_funcao', 'demissional'];

  // ---- validación local del CPF (los dígitos verificadores) ----
  function cpfOk(raw) {
    const d = String(raw || '').replace(/\D/g, '');
    if (d.length !== 11 || /^(\d)\1+$/.test(d)) return false;
    const dv = (n) => { let s = 0; for (let i = 0; i < n; i++) s += Number(d[i]) * (n + 1 - i); const r = (s * 10) % 11; return r === 10 ? 0 : r; };
    return dv(9) === Number(d[9]) && dv(10) === Number(d[10]);
  }
  const fmtCpf = (d) => String(d).replace(/\D/g, '').replace(/(\d{3})(\d{3})(\d{3})(\d{0,2})/, (m, a, b, c, e) => [a, b, c].join('.') + (e ? '-' + e : ''));

  // ---- API ----
  function apiBase() { return (typeof totemMarket !== 'undefined' && totemMarket.apiBase) || 'https://api.medicalcaresaas.com.br/v1'; }
  const real = () => typeof totemOrgSlug !== 'undefined' && !!totemOrgSlug;
  async function post(path, body) {
    const res = await fetch(`${apiBase()}/medical/public/totem/${encodeURIComponent(totemOrgSlug)}/occupational/${path}`, {
      method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body),
    });
    return res.json();
  }

  // ---- Bluetooth (perfiles GATT estándar) ----
  function sfloat(dv, off) {
    const raw = dv.getUint16(off, true);
    let mant = raw & 0x0fff; let exp = raw >> 12;
    if (mant >= 0x0800) mant -= 0x1000;
    if (exp >= 0x08) exp -= 0x10;
    return mant * Math.pow(10, exp);
  }
  function parseBloodPressure(dv) {
    const flags = dv.getUint8(0);
    let sys = sfloat(dv, 1); let dia = sfloat(dv, 3);
    if (flags & 1) { sys *= 7.50062; dia *= 7.50062; } // kPa → mmHg
    let off = 7; if (flags & 2) off += 7;
    const pulse = (flags & 4) && dv.byteLength >= off + 2 ? sfloat(dv, off) : null;
    return { systolic: Math.round(sys), diastolic: Math.round(dia), heartRate: pulse ? Math.round(pulse) : null };
  }
  function parseWeight(dv) {
    const flags = dv.getUint8(0); const imperial = flags & 1;
    const w = dv.getUint16(1, true);
    const weightKg = imperial ? w * 0.01 * 0.45359237 : w * 0.005;
    let off = 3; if (flags & 2) off += 7; if (flags & 4) off += 1;
    let heightCm = null;
    if ((flags & 8) && dv.byteLength >= off + 4) {
      const h = dv.getUint16(off + 2, true);
      heightCm = imperial ? h * 0.1 * 2.54 : h * 0.1; // SI: 0,001 m = 0,1 cm
    }
    return { weightKg: Math.round(weightKg * 10) / 10, heightCm: heightCm ? Math.round(heightCm) : null };
  }
  function readGatt(serviceName, charName, parse, timeoutMs) {
    return new Promise(async (resolve, reject) => {
      let device; let timer;
      try {
        device = await navigator.bluetooth.requestDevice({ filters: [{ services: [serviceName] }] });
        const server = await device.gatt.connect();
        const svc = await server.getPrimaryService(serviceName);
        const ch = await svc.getCharacteristic(charName);
        timer = setTimeout(() => { try { device.gatt.disconnect(); } catch (_) {} reject(new Error('timeout')); }, timeoutMs || 90000);
        ch.addEventListener('characteristicvaluechanged', (e) => {
          try {
            const r = parse(e.target.value);
            clearTimeout(timer);
            try { device.gatt.disconnect(); } catch (_) {}
            resolve(r);
          } catch (err) { /* sigue esperando */ }
        });
        await ch.startNotifications();
      } catch (e) { clearTimeout(timer); try { device && device.gatt && device.gatt.disconnect(); } catch (_) {} reject(e); }
    });
  }

  // ---- impresión ----
  function escpos(t, org, data) {
    const ESC = 0x1b, GS = 0x1d; const buf = [];
    const text = (s) => { const enc = typeof latinEncode === 'function' ? latinEncode(String(s || '')) : String(s || ''); for (const c of enc) buf.push(c.charCodeAt(0) & 0xff); };
    const nl = () => buf.push(0x0a);
    buf.push(ESC, 0x40, ESC, 0x74, 0x00, ESC, 0x61, 0x01, ESC, 0x45, 0x01);
    text(org.name || 'Medical Care'); nl(); buf.push(ESC, 0x45, 0x00);
    text(t.printTitle); nl(); text('--------------------------------'); nl(); nl();
    buf.push(GS, 0x21, 0x22); text(data.ticketNumber); nl(); buf.push(GS, 0x21, 0x00); nl();
    text(data.patientName); nl(); text(data.examLabel + ' - ' + data.position); nl();
    if (data.company) { text(data.company); nl(); }
    text(new Date().toLocaleString(typeof totemDateLocale !== 'undefined' ? totemDateLocale : 'pt-BR')); nl();
    nl(); nl(); nl(); buf.push(GS, 0x56, 0x01);
    return new Uint8Array(buf);
  }

  // ---- UI ----
  const BIG = { height: 72, fontSize: 24, borderRadius: 16, padding: '0 20px', width: '100%',
    background: 'rgba(255,255,255,0.06)', border: '1.5px solid rgba(255,255,255,0.14)', color: 'white', fontFamily: 'inherit' };

  function TextField({ value, onChange, placeholder, autoFocus, inputMode, maxLength }) {
    return <input style={BIG} value={value} placeholder={placeholder} autoFocus={autoFocus} inputMode={inputMode} maxLength={maxLength}
      onChange={(e) => onChange(e.target.value)} />;
  }

  function NumField({ label, value, onChange, unit, placeholder }) {
    return (
      <label style={{ display: 'block' }}>
        <span className="h-label" style={{ display: 'block', marginBottom: 6 }}>{label}</span>
        <div style={{ position: 'relative' }}>
          <input style={{ ...BIG, height: 58, fontSize: 26, fontVariantNumeric: 'tabular-nums' }} inputMode="decimal" value={value} placeholder={placeholder}
            onChange={(e) => onChange(e.target.value.replace(/[^\d.,]/g, '').slice(0, 6))} />
          {unit && <span style={{ position: 'absolute', right: 14, top: 18, color: '#8A93A6', fontSize: 15 }}>{unit}</span>}
        </div>
      </label>
    );
  }

  function OccupationalFlow({ org, onExit }) {
    const t = T();
    const [stage, setStage] = uS('cpf'); // cpf | name | confirm | type | role | vitals | sending | ticket | error
    const [cpf, setCpf] = uS('');
    const [cpfErr, setCpfErr] = uS(false);
    const [known, setKnown] = uS(null); // { patientName, lastPosition } | null
    const [fullName, setFullName] = uS('');
    const [examType, setExamType] = uS('');
    const [position, setPosition] = uS('');
    const [company, setCompany] = uS('');
    const [v, setV] = uS({ sys: '', dia: '', hr: '', weight: '', height: '' });
    const [bt, setBt] = uS({ busy: null, msg: '' });
    const [err, setErr] = uS('');
    const [ticket, setTicket] = uS(null);
    const [count, setCount] = uS(25);
    const [busy, setBusy] = uS(false);
    const maxLen = 11;

    // Recorrido del simulador (sólo si se abrió directo por enlace y hay consentimiento; ver track.js).
    const track = (e, d) => { try { window.TotemTrack && window.TotemTrack.beacon && window.TotemTrack.beacon(e, d); } catch (_) {} };
    uE(() => { track('occ', stage); }, [stage]);
    uE(() => { if (examType) track('exam', examType); }, [examType]);

    uE(() => {
      if (stage !== 'ticket') return;
      if (count <= 0) { onExit(); return; }
      const h = setTimeout(() => setCount((c) => c - 1), 1000);
      return () => clearTimeout(h);
    }, [stage, count]);

    const press = (n) => { setCpfErr(false); setCpf((d) => (d.length < maxLen ? d + n : d)); };
    const num = (s) => { const x = parseFloat(String(s).replace(',', '.')); return Number.isFinite(x) ? x : null; };
    const bmi = (() => { const w = num(v.weight), h = num(v.height); return w && h ? Math.round((w / Math.pow(h / 100, 2)) * 10) / 10 : null; })();

    const submitCpf = async () => {
      // Modo demonstración (sin ?org=): vale CUALQUIER CPF de 11 dígitos, para poder probar sin datos reales.
      // Con clínica real se validan los dígitos verificadores.
      const okCpf = real() ? cpfOk(cpf) : String(cpf).replace(/\D/g, '').length === 11;
      track('cpf', okCpf ? (real() ? 'valid' : 'demo-any') : 'invalid');
      if (!okCpf) { setCpfErr(true); return; }
      if (!real()) { setKnown(null); setStage('name'); return; } // demo
      setBusy(true);
      try {
        const r = await post('lookup', { documentNumber: cpf });
        setBusy(false);
        if (!r || r.success !== true) {
          setErr(r && r.reason === 'not_enabled' ? t.errNotEnabled : r && r.reason === 'invalid_document' ? t.invalidCpf : t.errGeneric);
          setStage('error'); return;
        }
        if (r.found) { setKnown({ patientName: r.patientName, lastPosition: r.lastPosition }); if (r.lastPosition) setPosition(r.lastPosition); setStage('confirm'); }
        else { setKnown(null); setStage('name'); }
      } catch (e) { setBusy(false); setErr(t.errGeneric); setStage('error'); }
    };

    const measure = async (kind) => {
      if (!navigator.bluetooth) { setBt({ busy: null, msg: t.btUnsupported }); return; }
      setBt({ busy: kind, msg: t.btWaiting });
      try {
        if (kind === 'bp') {
          const r = await readGatt('blood_pressure', 'blood_pressure_measurement', parseBloodPressure);
          setV((x) => ({ ...x, sys: String(r.systolic), dia: String(r.diastolic), hr: r.heartRate ? String(r.heartRate) : x.hr }));
        } else {
          const r = await readGatt('weight_scale', 'weight_measurement', parseWeight);
          setV((x) => ({ ...x, weight: String(r.weightKg), height: r.heightCm ? String(r.heightCm) : x.height }));
        }
        setBt({ busy: null, msg: t.btDone });
      } catch (e) { setBt({ busy: null, msg: t.btFail }); }
    };

    const send = async (skipVitals) => {
      setStage('sending');
      const payload = {
        documentNumber: cpf, fullName: known ? undefined : fullName, examType, position: position.trim(), companyName: company.trim(),
        vitals: skipVitals ? {} : { systolic: num(v.sys), diastolic: num(v.dia), heartRate: num(v.hr), weightKg: num(v.weight), heightCm: num(v.height) },
      };
      const examLabel = t[examType];
      try {
        let r;
        if (real()) r = await post('checkin', payload);
        else { await new Promise((ok) => setTimeout(ok, 700)); r = { success: true, ticketNumber: 'MT-' + String(Math.floor(1 + Math.random() * 40)).padStart(3, '0'), queuePosition: 3, patientName: fullName || (known && known.patientName) || 'Demonstração', isNew: !known }; }
        if (!r || r.success !== true) {
          setErr(r && r.reason === 'invalid_vital' ? t.errInvalidVital : r && r.reason === 'not_enabled' ? t.errNotEnabled : t.errGeneric);
          setStage(r && r.reason === 'invalid_vital' ? 'vitals' : 'error');
          return;
        }
        setTicket({ ...r, examLabel, position: position.trim(), company: company.trim() });
        setCount(25);
        setStage('ticket');
      } catch (e) { setErr(t.errGeneric); setStage('error'); }
    };

    const print = async () => {
      if (navigator.usb && ticket && typeof printViaWebUsb === 'function') {
        try { await printViaWebUsb(escpos(t, org, ticket)); return; } catch (_) { /* cae a window.print */ }
      }
      window.print();
    };

    const header = (step, onBack) => (
      <StepHeader step={step} total={5} label={t.step} onBack={onBack} onCancel={onExit} />
    );
    const css = `
      .oc-wrap { width: 100%; max-width: 1080px; margin: 0 auto; padding: 0 24px; }
      .oc-grid { display: grid; grid-template-columns: minmax(0, 480px) auto; gap: 56px; align-items: center; justify-content: center; }
      .oc-types { display: grid; grid-template-columns: repeat(2, 1fr); gap: 14px; margin-top: 18px; }
      .oc-type { appearance: none; text-align: left; padding: 22px; border-radius: 18px; background: rgba(255,255,255,0.05);
        border: 1.5px solid rgba(255,255,255,0.12); color: white; cursor: pointer; font-family: inherit; }
      .oc-type:hover { background: rgba(255,255,255,0.09); }
      .oc-type-on { border-color: var(--t-primary); background: rgba(30,144,255,0.14); }
      .oc-type b { display: block; font-size: 24px; }
      .oc-type span { color: #8A93A6; font-size: 15px; }
      .oc-vgrid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; margin-top: 10px; }
      .oc-bt { display: flex; gap: 12px; margin-top: 10px; flex-wrap: wrap; }
      .oc-msg { margin-top: 6px; color: #5DD3FF; font-size: 14px; min-height: 18px; }
      .oc-actions { display: flex; gap: 12px; margin-top: 16px; }
      .oc-actions .btn { flex: 1; }
      .oc-ticket { text-align: center; padding: 18px 0; }
      .oc-ticket-num { font-size: 120px; font-weight: 800; letter-spacing: 4px; line-height: 1.05; margin: 14px 0; font-variant-numeric: tabular-nums; }
      html.is-phone .oc-wrap { padding: 0 16px; }
      html.is-phone .oc-grid { grid-template-columns: 1fr; gap: 22px; justify-items: stretch; }
      html.is-phone .oc-types { grid-template-columns: 1fr; gap: 10px; }
      html.is-phone .oc-type { padding: 16px 18px; }
      html.is-phone .oc-type b { font-size: 20px; }
      html.is-phone .oc-vgrid { grid-template-columns: repeat(2, 1fr); }
      html.is-phone .oc-actions { flex-direction: column-reverse; }
      html.is-phone .oc-ticket-num { font-size: 84px; }
      html.is-phone .oc-bt .btn { width: 100%; }
      html.is-phone .oc-actions .btn { flex: 0 0 auto; width: 100%; }
    `;

    let body;
    if (stage === 'cpf') {
      body = (
        <div className="oc-wrap">
          {header(1, null)}
          <div className="oc-grid">
            <div>
              <h2 className="h-title">{t.cpfTitle}</h2>
              <p className="h-body" style={{ marginTop: 14, marginBottom: 26 }}>{real() ? t.cpfHint : t.cpfHintDemo}</p>
              <div style={{ ...BIG, height: 84, display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 38, letterSpacing: 2,
                fontVariantNumeric: 'tabular-nums', borderColor: cpfErr ? '#FF6B6B' : 'rgba(255,255,255,0.14)', color: cpf ? 'white' : '#5A6275' }}>
                {cpf ? fmtCpf(cpf) : '000.000.000-00'}
              </div>
              <div style={{ marginTop: 12, minHeight: 22, fontSize: 14, color: cpfErr ? '#FF6B6B' : '#8A93A6' }}>
                {cpfErr ? t.invalidCpf : ''}
              </div>
              <button className="btn btn-primary btn-block btn-xl" style={{ marginTop: 22 }} disabled={cpf.length < 11 || busy} onClick={submitCpf}>
                {busy ? t.checking : t.continue} <Icon name="arrow-right" size={26} />
              </button>
            </div>
            <NumericKeypad onPress={press} onBackspace={() => { setCpfErr(false); setCpf((d) => d.slice(0, -1)); }} onClear={() => { setCpfErr(false); setCpf(''); }} />
          </div>
        </div>
      );
    } else if (stage === 'confirm') {
      body = (
        <div className="oc-wrap" style={{ maxWidth: 760 }}>
          {header(1, () => setStage('cpf'))}
          <h2 className="h-title">{t.hello} {known && known.patientName}</h2>
          <p className="h-body" style={{ margin: '14px 0 26px' }}>{t.isYou}</p>
          <div className="oc-actions">
            <button className="btn btn-secondary btn-xl" onClick={() => { setKnown(null); setCpf(''); setStage('cpf'); }}>{t.no}</button>
            <button className="btn btn-primary btn-xl" onClick={() => setStage('type')}>{t.yes}</button>
          </div>
        </div>
      );
    } else if (stage === 'name') {
      body = (
        <div className="oc-wrap" style={{ maxWidth: 760 }}>
          {header(2, () => setStage('cpf'))}
          <h2 className="h-title">{t.nameTitle}</h2>
          <p className="h-body" style={{ margin: '12px 0 22px' }}>{t.nameHint}</p>
          <TextField value={fullName} onChange={setFullName} placeholder={t.namePh} autoFocus maxLength={120} />
          <div className="oc-actions">
            <button className="btn btn-primary btn-xl" disabled={fullName.trim().split(/\s+/).length < 2 || fullName.trim().length < 5} onClick={() => setStage('type')}>
              {t.continue} <Icon name="arrow-right" size={24} />
            </button>
          </div>
        </div>
      );
    } else if (stage === 'type') {
      body = (
        <div className="oc-wrap">
          {header(3, () => setStage(known ? 'confirm' : 'name'))}
          <h2 className="h-title">{t.typeTitle}</h2>
          <p className="h-body" style={{ marginTop: 8 }}>{t.typeHint}</p>
          <div className="oc-types">
            {EXAMS.map((k) => (
              <button key={k} className={'oc-type ' + (examType === k ? 'oc-type-on' : '')} onClick={() => { setExamType(k); setStage('role'); }}>
                <b>{t[k]}</b><span>{t[k + 'Sub']}</span>
              </button>
            ))}
          </div>
        </div>
      );
    } else if (stage === 'role') {
      body = (
        <div className="oc-wrap" style={{ maxWidth: 760 }}>
          {header(4, () => setStage('type'))}
          <h2 className="h-title">{t.roleTitle}</h2>
          <div style={{ display: 'grid', gap: 16, marginTop: 22 }}>
            <TextField value={position} onChange={setPosition} placeholder={t.rolePh} autoFocus maxLength={80} />
            <TextField value={company} onChange={setCompany} placeholder={t.companyPh} maxLength={120} />
          </div>
          <div className="oc-actions">
            <button className="btn btn-primary btn-xl" disabled={position.trim().length < 2} onClick={() => setStage('vitals')}>
              {t.continue} <Icon name="arrow-right" size={24} />
            </button>
          </div>
        </div>
      );
    } else if (stage === 'vitals') {
      body = (
        <div className="oc-wrap">
          {header(5, () => setStage('role'))}
          <h2 className="h-title" style={{ fontSize: 40 }}>{t.vitalsTitle}</h2>
          <p className="h-body" style={{ marginTop: 4, fontSize: 17 }}>{t.vitalsHint}</p>
          <div className="oc-bt">
            <button className="btn btn-secondary" disabled={!!bt.busy} onClick={() => measure('bp')}><Icon name="spark" size={20} /> {t.btPressure}</button>
            <button className="btn btn-secondary" disabled={!!bt.busy} onClick={() => measure('scale')}><Icon name="spark" size={20} /> {t.btScale}</button>
          </div>
          <div className="oc-msg">{bt.msg}</div>
          {err && <div className="oc-msg" style={{ color: '#FF6B6B' }}>{err}</div>}
          <div className="oc-vgrid">
            <NumField label={t.sys} value={v.sys} onChange={(x) => setV({ ...v, sys: x })} unit="mmHg" placeholder="120" />
            <NumField label={t.dia} value={v.dia} onChange={(x) => setV({ ...v, dia: x })} unit="mmHg" placeholder="80" />
            <NumField label={t.hr} value={v.hr} onChange={(x) => setV({ ...v, hr: x })} unit="bpm" placeholder="72" />
            <NumField label={t.weight} value={v.weight} onChange={(x) => setV({ ...v, weight: x })} unit="kg" placeholder="70" />
            <NumField label={t.height} value={v.height} onChange={(x) => setV({ ...v, height: x })} unit="cm" placeholder="170" />
            <div>
              <span className="h-label" style={{ display: 'block', marginBottom: 6 }}>{t.bmi}</span>
              <div style={{ ...BIG, height: 58, fontSize: 26, display: 'flex', alignItems: 'center', background: 'transparent' }}>{bmi || '—'}</div>
            </div>
          </div>
          <div className="oc-actions">
            <button className="btn btn-ghost btn-xl" onClick={() => { setV({ sys: '', dia: '', hr: '', weight: '', height: '' }); send(true); }}>{t.skip}</button>
            <button className="btn btn-primary btn-xl" onClick={() => { setErr(''); send(false); }}>{t.continue} <Icon name="arrow-right" size={24} /></button>
          </div>
        </div>
      );
    } else if (stage === 'sending') {
      body = <div className="oc-wrap oc-ticket"><h2 className="h-title">{t.sending}</h2></div>;
    } else if (stage === 'error') {
      body = (
        <div className="oc-wrap oc-ticket">
          <h2 className="h-title">{err || t.errGeneric}</h2>
          <div className="oc-actions" style={{ justifyContent: 'center', marginTop: 30 }}><button className="btn btn-primary btn-xl" onClick={onExit}>{t.finish}</button></div>
        </div>
      );
    } else if (stage === 'ticket' && ticket) {
      body = (
        <div className="oc-wrap oc-ticket">
          <h2 className="h-title">{t.ticketTitle}</h2>
          <p className="h-body">{t.ticketBody}</p>
          <div className="oc-ticket-num">{ticket.ticketNumber}</div>
          <p className="h-body">{ticket.patientName} · {ticket.examLabel}</p>
          <p className="h-body" style={{ marginTop: 6 }}>{t.ticketPos}: <b>#{ticket.queuePosition}</b></p>
          {ticket.isNew && <p className="h-body" style={{ marginTop: 6, color: '#5DD3FF' }}>{t.ticketNew}</p>}
          <div className="oc-actions" style={{ justifyContent: 'center', marginTop: 30 }}>
            <button className="btn btn-secondary btn-xl" onClick={print}><Icon name="printer" size={22} /> {t.print}</button>
            <button className="btn btn-primary btn-xl" onClick={onExit}>{t.finishIn(count)}</button>
          </div>
        </div>
      );
    }

    return (
      <div className="screen scale-in">
        <style>{css}</style>
        {body}
        {ticket && ReactDOM.createPortal(
          <div id="print-ticket">
            <div style={{ textAlign: 'center', fontFamily: 'monospace' }}>
              <div style={{ fontWeight: 700 }}>{org.name}</div>
              <div>{t.printTitle}</div>
              <div style={{ fontSize: 44, fontWeight: 800, margin: '10px 0' }}>{ticket.ticketNumber}</div>
              <div>{ticket.patientName}</div>
              <div>{ticket.examLabel} - {ticket.position}</div>
              {ticket.company ? <div>{ticket.company}</div> : null}
              <div style={{ marginTop: 8 }}>{new Date().toLocaleString(typeof totemDateLocale !== 'undefined' ? totemDateLocale : 'pt-BR')}</div>
            </div>
          </div>,
          document.body,
        )}
      </div>
    );
  }

  window.OccupationalFlow = OccupationalFlow;
})();
