/* global React, ReactDOM, MOCK_DB, tt, totemDateLocale, totemLocale, totemMarket, totemProfile, totemOrgSlug, totemFormatMoney,
   ScreenIdle, ScreenActionPicker, ScreenDni, ScreenQrScan, ScreenNotFound,
   ScreenConfirm, ScreenAppointments, ScreenVisitReason, ScreenCoverage,
   ScreenLabOrders, ScreenSummary, ScreenTicket, TicketContent,
   TotemLogo, Icon, Tag,
   TweaksPanel, TweakSection, TweakColor, TweakRadio, TweakToggle, TweakText, TweakSlider,
   useTweaks */
const { useState, useEffect, useRef, useMemo, useCallback } = React;

function urlHex(name, fallback) {
  try {
    var v = new URLSearchParams(window.location.search).get(name);
    if (!v) return fallback;
    if (v.charAt(0) !== '#') v = '#' + v;
    if (/^#[0-9A-Fa-f]{6}$/.test(v)) return v;
  } catch (_) {}
  return fallback;
}

// ---------- Tweak defaults ----------
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "primaryColor": urlHex('primary', '#1E90FF'),
  "accentColor": urlHex('accent', '#5DD3FF'),
  "orgName": (typeof totemProfile !== 'undefined' && totemProfile === 'pharmacy')
    ? ((typeof totemLocale !== 'undefined' && totemLocale === 'en-US') ? "Medical Care Pharmacy" : (typeof totemMarket !== 'undefined' && totemMarket.code === 'BR') ? "Farmácia Medical Care" : "Farmacia Medical Care")
    : (typeof totemProfile !== 'undefined' && totemProfile === 'lab')
    ? ((typeof totemLocale !== 'undefined' && totemLocale === 'en-US') ? "Medical Care Laboratory" : (typeof totemMarket !== 'undefined' && totemMarket.code === 'BR') ? "Laboratório Medical Care" : "Laboratorio Medical Care")
    : (typeof totemProfile !== 'undefined' && totemProfile === 'hospital')
    ? "Hospital Medical Care"
    : (typeof totemProfile !== 'undefined' && totemProfile === 'dental')
    ? ((typeof totemLocale !== 'undefined' && totemLocale === 'en-US') ? "Medical Care Dental" : (typeof totemMarket !== 'undefined' && totemMarket.code === 'BR') ? "Odontologia Medical Care" : "Odontología Medical Care")
    : "Medical Care SaaS",
  "orgLocation": (typeof totemMarket !== 'undefined' && totemMarket.code === 'BR') ? "Unidade central"
    : (typeof totemLocale !== 'undefined' && totemLocale === 'en-US') ? "Main office" : "Sede central",
  "showHelper": true,
  "showFrame": true,
  "kioskMode": "tablet"
} /*EDITMODE-END*/;

// ---------- Mock API (simulates network latency) ----------
const wait = (ms) => new Promise((r) => setTimeout(r, ms));
const api = {
  async findPatient(dni) {
    await wait(450);
    const digits = String(dni || '').replace(/\D/g, '');
    let p = MOCK_DB.patients[digits] || MOCK_DB.patients[dni];
    if (!p && MOCK_DB.docAlias && MOCK_DB.docAlias[digits]) {
      p = MOCK_DB.patients[MOCK_DB.docAlias[digits]];
    }
    if (!p && digits.length === 11) {
      p = MOCK_DB.patients[digits.slice(0, 8)];
    }
    if (!p) throw { status: 404, message: (typeof tt === 'function' ? tt('api.notFound') : 'not found') };
    return p;
  },
  async getAppointments(patientId) {
    await wait(380);
    return MOCK_DB.appointments[patientId] || [];
  },
  async getLabOrders(patientId) {
    await wait(380);
    return MOCK_DB.labOrders[patientId] || [];
  },
  async checkin(/* apptId */) {
    await wait(500);
    return { success: true };
  },
  async createWalkin(/* payload */) {
    await wait(500);
    return { success: true, id: "a_walkin_" + Math.random().toString(36).slice(2, 8) };
  }
};

// ---------- Real API (tótem conectado a una org real) ----------
// Solo se usa para flow === "appointment" cuando el tótem viene configurado
// con ?org=<slug> (o localStorage totem_org_slug) — sin eso, sigue en modo
// demo (MOCK_DB de arriba) para no mezclar check-ins reales por accidente.
function isRealOrgConfigured() {
  return typeof totemOrgSlug !== 'undefined' && !!totemOrgSlug;
}
function totemApiBase() {
  return (typeof totemMarket !== 'undefined' && totemMarket.apiBase) || 'https://api.medicalcaresaas.online/v1';
}
const realApi = {
  async lookup(payload) {
    const url = `${totemApiBase()}/medical/public/totem/${encodeURIComponent(totemOrgSlug)}/lookup`;
    const res = await fetch(url, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payload),
    });
    return res.json();
  },
  async checkin(appointmentId) {
    const url = `${totemApiBase()}/medical/public/totem/${encodeURIComponent(totemOrgSlug)}/checkin`;
    const res = await fetch(url, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ appointmentId }),
    });
    return res.json();
  },
};

// ============================================================
// ESC/POS — impresión USB térmica 58mm (Gadnic IT1050, etc.)
// ============================================================
function latinEncode(str) {
  // Transliterar caracteres ES/PT a ASCII para impresoras ESC/POS básicas
  return str
    .replace(/[áàäâã]/g,'a').replace(/[ÁÀÄÂÃ]/g,'A')
    .replace(/[éèëê]/g,'e').replace(/[ÉÈËÊ]/g,'E')
    .replace(/[íìïî]/g,'i').replace(/[ÍÌÏÎ]/g,'I')
    .replace(/[óòöôõ]/g,'o').replace(/[ÓÒÖÔÕ]/g,'O')
    .replace(/[úùüû]/g,'u').replace(/[ÚÙÜÛ]/g,'U')
    .replace(/ç/g,'c').replace(/Ç/g,'C')
    .replace(/ñ/g,'n').replace(/Ñ/g,'N')
    .replace(/[^\x00-\x7F]/g,'?');
}

function buildEscPos({ org, flow, patient, appointment, reason, coverage, labs, ticketNumber, queuePosition }) {
  const ESC = 0x1B, GS = 0x1D;
  const buf = [];
  const push = (...bytes) => buf.push(...bytes);
  const text = (s) => buf.push(...Array.from(latinEncode(s || ''), c => c.charCodeAt(0)));
  const nl = () => buf.push(0x0A);
  const line = (char = '-', n = 32) => { text(char.repeat(n)); nl(); };

  const date = new Date();
  const dl = (typeof totemDateLocale !== 'undefined' ? totemDateLocale : 'es-AR');
  const dStr = date.toLocaleDateString(dl, { day:'2-digit', month:'2-digit', year:'numeric' });
  const tStr = date.toLocaleTimeString(dl, { hour:'2-digit', minute:'2-digit' });
  const _t = (k) => (typeof tt === 'function' ? tt(k) : k);

  // Init + code page Latin
  push(ESC,0x40, ESC,0x74,0x00);
  // Center
  push(ESC,0x61,0x01);
  // Bold + double-height title
  push(ESC,0x45,0x01, GS,0x21,0x01);
  text(org.name || 'MedicalCare'); nl();
  push(GS,0x21,0x00, ESC,0x45,0x00);
  nl();

  // Tipo de atención
  const typeStr = flow==='appointment' ? _t('print.type.appt') :
                  flow==='walkin'      ? _t('print.type.walkin') :
                                        _t('print.type.labs');
  line('-');
  push(ESC,0x45,0x01); text(typeStr); nl(); push(ESC,0x45,0x00);
  line('-');

  // Número de turno grande
  if (flow !== 'labs' && ticketNumber) {
    nl();
    push(GS,0x21,0x22); // triple ancho+alto
    text(ticketNumber); nl();
    push(GS,0x21,0x00);
    text(_t('print.queue') + queuePosition); nl();
    nl();
  }

  // Datos paciente
  push(ESC,0x61,0x00); // izquierda
  line('-');
  const row = (label, value) => {
    const l = latinEncode(label).padEnd(14,' ').slice(0,14);
    text(l + ' ' + (value || '')); nl();
  };
  row(_t('summary.patient'), (patient?.firstName || '') + ' ' + (patient?.lastName || ''));
  row(_t('doc'), patient?.documentNumber || '');
  row(_t('ticket.date'), dStr);
  row(_t('ticket.checkin'), tStr);

  if (flow==='appointment' && appointment) {
    line('-');
    row(_t('ticket.apptTime'), appointment.time + (_t('time.hours') ? ' ' + _t('time.hours') : ''));
    row(_t('ticket.doctor'), appointment.doctorName);
    row(_t('ticket.specialty'), appointment.specialty);
    row(_t('ticket.room'), appointment.room);
    if (patient?.coverage?.provider) row(_t('summary.coverage'), patient.coverage.provider);
    if (appointment.copay > 0) row(_t('ticket.copay'), (typeof totemFormatMoney === 'function' ? totemFormatMoney(appointment.copay) : '$ ' + appointment.copay.toLocaleString(dl)));
  }
  if (flow==='walkin' && reason) {
    line('-');
    row(_t('ticket.reason'), reason.label);
    row(_t('summary.coverage'), coverage?.provider || _t('coverage.particular'));
  }
  if (flow==='labs' && labs) {
    line('-');
    text(_t('ticket.studies')); nl();
    labs.forEach(l => { text('  · ' + (l.studyType || '')); nl(); });
  }

  // Pie
  line('-');
  push(ESC,0x61,0x01);
  text(flow==='labs' ? _t('print.labsFooter') : _t('print.wait'));
  nl();
  text('www.medicalcaresaas.com'); nl();
  nl(); nl(); nl();
  // Corte parcial
  push(GS,0x56,0x01);

  return new Uint8Array(buf);
}

async function printViaWebUsb(escposData) {
  if (!navigator.usb) throw new Error('WebUSB not supported');
  let device;
  try {
    // Intentar obtener un dispositivo ya autorizado
    const devices = await navigator.usb.getDevices();
    device = devices.find(d => d.deviceClass===7 || d.configurations?.[0]?.interfaces?.some(i=>i.alternates?.[0]?.interfaceClass===7));
  } catch(_){}

  if (!device) {
    // Mostrar selector — filtrar por clase impresora (0x07)
    device = await navigator.usb.requestDevice({ filters: [{ classCode: 0x07 }] });
  }
  await device.open();
  try {
    if (device.configuration === null) await device.selectConfiguration(1);
    // Reclamar la interfaz de impresora
    const iface = device.configuration.interfaces.find(i =>
      i.alternates.some(a => a.interfaceClass === 7)
    ) || device.configuration.interfaces[0];
    await device.claimInterface(iface.interfaceNumber);
    const alt = iface.alternates.find(a => a.interfaceClass===7) || iface.alternates[0];
    const ep = alt.endpoints.find(e => e.direction==='out' && e.type==='bulk');
    if (!ep) throw new Error('No bulk-OUT endpoint found');
    await device.transferOut(ep.endpointNumber, escposData);
  } finally {
    try { await device.close(); } catch(_){}
  }
}

// ---------- Random ticket number ----------
function makeTicket(flow) {
  const prefix = flow === "appointment" ? "T"
    : flow === "walkin" ? "G"
    : flow === "rx" ? "R"
    : flow === "pickup" ? "P"
    : flow === "counter" ? "M"
    : flow === "draw" ? "E"
    : flow === "results" ? "X"
    : flow === "dropoff" ? "A"
    : flow === "er" ? "U"
    : flow === "admit" ? "I"
    : flow === "visit" ? "V"
    : flow === "odappt" ? "D"
    : flow === "odurgent" ? "DU"
    : flow === "odhygiene" ? "H"
    : "L";
  const num = String(Math.floor(40 + Math.random() * 50)).padStart(3, "0");
  return `${prefix}-${num}`;
}

// ---------- Loading veil ----------
function LoadingVeil({ message }) {
  return (
    <div className="loading-veil fade-in">
      <div className="loading-spinner" />
      <div className="loading-msg">{message}</div>
      <style>{`
        .loading-veil {
          position: absolute; inset: 0; z-index: 50;
          background: rgba(7,11,23,0.85);
          backdrop-filter: blur(6px);
          display: grid; place-items: center;
          gap: 18px;
          flex-direction: column;
        }
        .loading-spinner {
          width: 48px; height: 48px;
          border: 3px solid rgba(255,255,255,0.15);
          border-top-color: var(--t-primary);
          border-radius: 50%;
          animation: spin 0.8s linear infinite;
          margin: 0 auto;
        }
        .loading-msg {
          margin-top: 16px;
          text-align: center;
          color: #C7CCD9;
          font-size: 16px;
          letter-spacing: 0.02em;
        }
        @keyframes spin { to { transform: rotate(360deg); } }
      `}</style>
    </div>);

}

// ---------- Top bar ----------
function TopBar({ orgName, currentTime }) {
  return (
    <div className="topbar">
      <div className="brand">
        <div className="logo"><TotemLogo size={20} /></div>
        Totem MedicalCare
        <span className="org">· {orgName}</span>
      </div>
      <div className="meta">
        <span style={{ display: 'inline-flex', alignItems: 'center', gap: 8 }}>
          <Icon name="wifi" size={16} />
          {tt('top.connected')}
        </span>
        <span style={{ display: 'inline-flex', alignItems: 'center', gap: 6 }}>
          <span className="dot" /> {tt('top.online')}
        </span>
        <span style={{ fontVariantNumeric: 'tabular-nums' }}>{currentTime}</span>
      </div>
    </div>);

}

// ---------- Bottom bar ----------
function BottomBar({ stage, onCancel, helper }) {
  const stageLabel = {
    idle: tt('stage.idle'),
    pick: tt('stage.pick'),
    dni: tt('stage.dni'),
    notfound: tt('stage.notfound'),
    confirm: tt('stage.confirm'),
    appts: tt('stage.appts'),
    reason: tt('stage.reason'),
    coverage: tt('stage.coverage'),
    labs: tt('stage.labs'),
    summary: tt('stage.summary'),
    ticket: tt('stage.ticket')
  }[stage] || "";

  return (
    <div className="bottombar">
      <div style={{ display: 'flex', alignItems: 'center', gap: 14 }}>
        {stage !== "idle" && <span className="step-pill">{stageLabel}</span>}
        {stage === "idle" && <span style={{ color: '#5A6275', fontSize: 12, letterSpacing: '0.1em', textTransform: 'uppercase' }}>{(typeof totemProfile !== 'undefined' && totemProfile === 'pharmacy') ? tt('ph.bottom.idle') : (typeof totemProfile !== 'undefined' && totemProfile === 'lab') ? tt('labp.bottom.idle') : (typeof totemProfile !== 'undefined' && totemProfile === 'hospital') ? tt('hosp.bottom.idle') : (typeof totemProfile !== 'undefined' && totemProfile === 'dental') ? tt('od.bottom.idle') : tt('bottom.idle')}</span>}
      </div>
      <div className="secondary-actions">
        {helper && <span style={{ color: '#5A6275', fontSize: 12 }}>{helper}</span>}
        {stage !== "idle" && stage !== "ticket" &&
        <button className="btn btn-ghost" onClick={onCancel}>
            <Icon name="x" size={14} /> {tt('bottom.exit')}
          </button>
        }
      </div>
    </div>);

}

// ---------- Demo helper card ----------
// Arranca COLAPSADO (sólo una etiqueta chica) — antes se abría a pantalla
// completa por default y en pantallas más chicas (o al grabar video) tapaba
// el título de bienvenida y el botón de "Toque para comenzar" que quedan
// justo detrás. `onHide` (el toggle de arriba, tweak/sesión) sigue existiendo
// para sacarlo del todo; este estado local sólo pliega/despliega la tarjeta.
function DemoTip({ visible, onHide }) {
  const [expanded, setExpanded] = useState(false);
  if (!visible) return null;

  if (!expanded) {
    return (
      <button
        onClick={() => setExpanded(true)}
        className="demo-tip demo-tip-chip"
      >
        {tt('demo.title')}
      </button>
    );
  }

  return (
    <div className="demo-tip demo-tip-expanded">
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 6 }}>
        <h4 style={{ margin: 0 }}>{tt('demo.title')}</h4>
        <button
          onClick={() => setExpanded(false)}
          aria-label={tt('demo.hide')}
          style={{ background: 'transparent', border: 0, color: '#8A93A6', cursor: 'pointer', fontSize: 16, lineHeight: 1, padding: 0 }}
        >×</button>
      </div>
      {(typeof totemMarket !== 'undefined' && totemMarket.code === 'BR') ? (<>
        <div className="tip-row"><code>30123456045</code><span>María · {tt('doc')}{(typeof totemProfile !== 'undefined' && totemProfile === 'pharmacy') ? ' · ' + tt('ph.flow.rx') : ''}</span></div>
        <div className="tip-row"><code>33456789009</code><span>Ana · {tt('doc')}</span></div>
        <div className="tip-row"><code>28111222068</code><span>Luis · {tt('demo.tag.labs')}</span></div>
        <div className="tip-row"><code>52998224725</code><span>María · alt</span></div>
      </>) : (<>
        <div className="tip-row"><code>30123456</code><span>María González · 2</span></div>
        <div className="tip-row"><code>25789012</code><span>Carlos Rodríguez · 1</span></div>
        <div className="tip-row"><code>33456789</code><span>Ana Martínez</span></div>
        <div className="tip-row"><code>28111222</code><span>Luis Fernández · labs</span></div>
        <div className="tip-row"><code>40987654</code><span>Sofía Vega · 1</span></div>
      </>)}
      <div style={{ marginTop: 8, fontSize: 11, color: '#5A6275', fontFamily: 'Inter' }}>
        {tt('demo.other')}
        <button onClick={onHide} style={{ marginLeft: 8, background: 'transparent', border: 0, color: '#1E90FF', cursor: 'pointer', fontSize: 11, padding: 0, fontFamily: 'Inter' }}>{tt('demo.hide')}</button>
      </div>
    </div>);

}

// ============================================================
// ROOT APP
// ============================================================
function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const isDemo = new URLSearchParams(window.location.search).get('demo') === '1';

  // Derived organisation
  const org = { ...MOCK_DB.org, name: t.orgName, location: t.orgLocation };

  // Apply theme colors via CSS variables
  useEffect(() => {
    document.documentElement.style.setProperty('--t-primary', t.primaryColor);
    document.documentElement.style.setProperty('--t-accent', t.accentColor);
    document.documentElement.style.setProperty('--t-primary-soft', t.primaryColor + '24');
    document.documentElement.style.setProperty('--t-primary-2', shade(t.primaryColor, -20));
  }, [t.primaryColor, t.accentColor]);

  // Live update from tweaks-panel events (when persisted)
  // (no-op: useTweaks already drives state)

  // Stage state
  const [stage, setStage] = useState("idle");
  const [flow, setFlow] = useState(null); // appointment | walkin | labs
  const [dni, setDni] = useState("");
  const [patient, setPatient] = useState(null);
  const [appointments, setAppointments] = useState([]);
  const [appointment, setAppointment] = useState(null);
  const [realCheckin, setRealCheckin] = useState(false); // true si este check-in va contra el backend real, no MOCK_DB
  const [labOrders, setLabOrders] = useState([]);
  const [selectedLabs, setSelectedLabs] = useState(null);
  const [reason, setReason] = useState(null);
  const [coverage, setCoverage] = useState(null);
  const [ticketData, setTicketData] = useState(null);
  const [loading, setLoading] = useState(null);
  const [showHelper, setShowHelper] = useState(true);

  const [now, setNow] = useState(new Date());
  useEffect(() => {
    const t = setInterval(() => setNow(new Date()), 30 * 1000);
    return () => clearInterval(t);
  }, []);
  const timeStr = now.toLocaleTimeString(typeof totemDateLocale !== 'undefined' ? totemDateLocale : 'es-AR', { hour: '2-digit', minute: '2-digit' });

  // Auto-reset timer (idle after inactivity at ticket stage handled in ticket screen)
  // Reset full flow
  const reset = useCallback(() => {
    setStage("idle");
    setFlow(null);
    setDni("");
    setPatient(null);
    setAppointments([]);
    setAppointment(null);
    setLabOrders([]);
    setSelectedLabs(null);
    setReason(null);
    setCoverage(null);
    setTicketData(null);
    setLoading(null);
    setRealCheckin(false);
  }, []);

  // ========= Step handlers =========
  const startFlow = () => setStage("pick");

  const pickFlow = (f) => {
    setFlow(f);
    setStage("dni");
  };

  const submitDni = async (d) => {
    setDni(d);
    setLoading(tt('load.findPatient'));
    if (flow === "appointment" && isRealOrgConfigured()) {
      try {
        const result = await realApi.lookup({ documentNumber: d });
        setLoading(null);
        if (!result || result.success !== true) {
          setRealCheckin(false);
          setStage("notfound");
          return;
        }
        const parts = String(result.patientName || '').trim().split(/\s+/);
        setPatient({ id: null, firstName: parts[0] || '', lastName: parts.slice(1).join(' ') });
        setAppointments(result.appointments || []);
        setRealCheckin(true);
        setStage("confirm");
      } catch (e) {
        setLoading(null);
        setRealCheckin(false);
        setStage("notfound");
      }
      return;
    }
    setRealCheckin(false);
    try {
      const p = await api.findPatient(d);
      setPatient(p);
      setLoading(null);
      setStage("confirm");
    } catch {
      setLoading(null);
      setStage("notfound");
    }
  };

  // Extrae el token del Health Passport de lo que decodificó la cámara: puede
  // ser la URL completa (.../mi/<token> o .../p/<token>) o el token pelado.
  const extractPassportToken = (scanned) => {
    const raw = String(scanned || '').trim();
    if (!raw) return '';
    const withoutQuery = raw.split(/[?#]/)[0];
    const parts = withoutQuery.split('/').filter(Boolean);
    return parts.length ? parts[parts.length - 1] : raw;
  };

  const onScanQr = async (scanned) => {
    const token = extractPassportToken(scanned);
    if (!token) { setStage("notfound"); return; }
    setLoading(tt('load.findPatient'));
    try {
      const result = await realApi.lookup({ passportToken: token });
      setLoading(null);
      if (!result || result.success !== true) {
        setRealCheckin(false);
        setStage("notfound");
        return;
      }
      const parts = String(result.patientName || '').trim().split(/\s+/);
      setPatient({ id: null, firstName: parts[0] || '', lastName: parts.slice(1).join(' ') });
      setAppointments(result.appointments || []);
      setRealCheckin(true);
      setStage("confirm");
    } catch (e) {
      setLoading(null);
      setRealCheckin(false);
      setStage("notfound");
    }
  };

  const onConfirmIdentity = async () => {
    if (flow === "appointment") {
      if (realCheckin) {
        // Ya se trajeron con el lookup real en submitDni — no hay que repetir el pedido.
        if (appointments.length === 0) {
          setStage("noappts");
        } else if (appointments.length === 1) {
          setAppointment(appointments[0]);
          setStage("summary");
        } else {
          setStage("appts");
        }
        return;
      }
      setLoading(tt('load.findAppts'));
      const list = await api.getAppointments(patient.id);
      setLoading(null);
      if (list.length === 0) {
        // No turnos → suggest walk-in
        setLoading(null);
        setStage("noappts");
      } else {
        setAppointments(list);
        if (list.length === 1) {
          setAppointment(list[0]);
          setStage("summary");
        } else {
          setStage("appts");
        }
      }
    } else if (flow === "walkin" || flow === "rx" || flow === "draw" || flow === "er" || flow === "odurgent" || flow === "odhygiene") {
      setStage("reason");
    } else if (flow === "admit" || flow === "odappt") {
      setStage("coverage");
    } else if (flow === "counter" || flow === "dropoff" || flow === "visit") {
      setStage("summary");
    } else if (flow === "labs" || flow === "pickup" || flow === "results") {
      setLoading(flow === "pickup" ? tt('ph.load.pickup') : tt('load.findLabs'));
      const list = await api.getLabOrders(patient.id);
      setLoading(null);
      if (list.length === 0) {
        setStage("nolabs");
      } else {
        setLabOrders(list);
        setStage("labs");
      }
    }
  };

  const onApptSelected = (a) => {
    setAppointment(a);
    setStage("summary");
  };

  const onReason = (r) => {
    setReason(r);
    setStage("coverage");
  };

  const onCoverage = (c) => {
    setCoverage(c);
    setStage("summary");
  };

  const onLabsSelected = (orders) => {
    setSelectedLabs(orders);
    setStage("summary");
  };

  const onConfirmCheckin = async () => {
    setLoading(flow === "appointment" ? tt('load.checkin') :
    flow === "walkin" ? tt('load.walkin') :
    flow === "rx" ? tt('ph.load.rx') :
    flow === "counter" ? tt('ph.load.counter') :
    flow === "pickup" ? tt('ph.load.pickup') :
    flow === "draw" ? tt('labp.load.draw') :
    flow === "dropoff" ? tt('labp.load.dropoff') :
    flow === "er" ? tt('hosp.load.er') :
    flow === "admit" ? tt('hosp.load.admit') :
    flow === "visit" ? tt('hosp.load.visit') :
    flow === "odappt" ? tt('od.load.appt') :
    flow === "odurgent" ? tt('od.load.urgent') :
    flow === "odhygiene" ? tt('od.load.hygiene') :
    tt('load.labs'));
    if (flow === "appointment" && realCheckin) {
      try {
        const result = await realApi.checkin(appointment.id);
        if (!result || result.success !== true) {
          setLoading(null);
          setStage("notfound");
          return;
        }
      } catch (e) {
        setLoading(null);
        setStage("notfound");
        return;
      }
    } else
    if (flow === "appointment") await api.checkin(appointment.id);else
    if (flow === "walkin" || flow === "rx" || flow === "counter" || flow === "draw" || flow === "dropoff" || flow === "er" || flow === "admit" || flow === "visit" || flow === "odappt" || flow === "odurgent" || flow === "odhygiene") await api.createWalkin();else
    await wait(500);

    const ticketNumber = makeTicket(flow);
    const queuePosition = Math.floor(2 + Math.random() * 8);
    const eta = flow === "appointment" ? 5 + Math.floor(Math.random() * 8) : 12 + Math.floor(Math.random() * 18);
    setTicketData({ ticketNumber, queuePosition, eta });
    setLoading(null);
    setStage("ticket");
  };

  const triggerPrint = async () => {
    // Intentar impresión directa via WebUSB (impresora térmica ESC/POS)
    if (navigator.usb && ticketData && patient) {
      try {
        const escposData = buildEscPos({
          org, flow, patient, appointment,
          reason, coverage, labs: selectedLabs,
          ticketNumber: ticketData.ticketNumber,
          queuePosition: ticketData.queuePosition,
        });
        await printViaWebUsb(escposData);
        return; // impresión USB exitosa, no hacer window.print()
      } catch (err) {
        console.warn('[Tótem] WebUSB print falló, usando window.print():', err.message);
      }
    }
    // Fallback: imprimir via CSS @media print
    window.print();
  };

  // ========= Render =========
  let screen;
  if (stage === "idle") {
    screen = <ScreenIdle org={org} onStart={startFlow} />;
  } else if (stage === "pick") {
    screen = <ScreenActionPicker onPick={pickFlow} onBack={reset} />;
  } else if (stage === "dni") {
    screen = <ScreenDni flow={flow} onSubmit={submitDni}
    onBack={() => setStage("pick")} onCancel={reset}
    onScanQr={(flow === "appointment" && isRealOrgConfigured()) ? () => setStage("qrscan") : null} />;
  } else if (stage === "qrscan") {
    screen = <ScreenQrScan onScan={onScanQr}
    onBack={() => setStage("dni")} onCancel={reset}
    onFallbackDni={() => setStage("dni")} />;
  } else if (stage === "notfound") {
    screen = <ScreenNotFound dni={dni}
    onRetry={() => {setDni("");setStage("dni");}}
    onCancel={reset} />;
  } else if (stage === "confirm") {
    screen = <ScreenConfirm patient={patient}
    onYes={onConfirmIdentity}
    onNo={() => {setPatient(null);setDni("");setStage("dni");}}
    onCancel={reset} />;
  } else if (stage === "appts") {
    screen = <ScreenAppointments patient={patient} appointments={appointments}
    onSelect={onApptSelected}
    onBack={() => setStage("confirm")}
    onCancel={reset} />;
  } else if (stage === "reason") {
    screen = <ScreenVisitReason reasons={
      (typeof totemProfile !== 'undefined' && totemProfile === 'pharmacy') ? MOCK_DB.pharmacyReasons
      : (typeof totemProfile !== 'undefined' && totemProfile === 'lab') ? MOCK_DB.labReasons
      : (typeof totemProfile !== 'undefined' && totemProfile === 'hospital') ? MOCK_DB.hospitalReasons
      : (typeof totemProfile !== 'undefined' && totemProfile === 'dental')
        ? (flow === 'odurgent' ? MOCK_DB.dentalUrgentReasons : MOCK_DB.dentalReasons)
      : MOCK_DB.visitReasons}
    onSelect={onReason}
    onBack={() => setStage("confirm")}
    onCancel={reset} />;
  } else if (stage === "coverage") {
    // If patient already has coverage, auto-fill but still let them confirm
    const pre = patient?.coverage ? [{ id: 'preselected', name: patient.coverage.provider }] : [];
    screen = <ScreenCoverage providers={[...pre, ...MOCK_DB.providers]}
    onSubmit={onCoverage}
    onBack={() => setStage("reason")}
    onCancel={reset} />;
  } else if (stage === "labs") {
    screen = <ScreenLabOrders patient={patient} orders={labOrders}
    onConfirm={onLabsSelected}
    onBack={() => setStage("confirm")}
    onCancel={reset} />;
  } else if (stage === "noappts" || stage === "nolabs") {
    screen =
    <NoneFound
      title={stage === "noappts" ? tt('none.appts.title') : (flow === "pickup" ? tt('ph.none.pickup.title') : tt('none.labs.title'))}
      message={stage === "noappts" ?
      tt('none.appts.msg') :
      (flow === "pickup" ? tt('ph.none.pickup.msg') : tt('none.labs.msg'))}
      primary={stage === "noappts" ? { label: tt('none.appts.cta'), onClick: () => {setFlow('walkin');setStage('reason');} } : null}
      secondary={{ label: tt('none.home'), onClick: reset }} />;


  } else if (stage === "summary") {
    screen = <ScreenSummary
      flow={flow} patient={patient} appointment={appointment}
      reason={reason} coverage={coverage} labs={selectedLabs}
      onConfirm={onConfirmCheckin}
      onBack={() => {
        if (flow === "appointment") setStage(appointments.length > 1 ? "appts" : "confirm");else
        if (flow === "walkin" || flow === "rx" || flow === "draw" || flow === "er" || flow === "odurgent" || flow === "odhygiene") setStage("coverage");else
        if (flow === "admit" || flow === "odappt") setStage("coverage");else
        if (flow === "counter" || flow === "dropoff" || flow === "visit") setStage("confirm");else
        setStage("labs");
      }}
      onCancel={reset} />;
  } else if (stage === "ticket") {
    screen = <ScreenTicket
      flow={flow} patient={patient} appointment={appointment}
      reason={reason} coverage={coverage} labs={selectedLabs}
      ticketNumber={ticketData.ticketNumber}
      queuePosition={ticketData.queuePosition}
      eta={ticketData.eta}
      org={org}
      onPrint={triggerPrint}
      onReset={reset} />;
  }

  // Tablet wrap toggle
  const tablet = t.showFrame !== false;

  return (
    <div className="stage" style={{ '--scale-base': 1 }}>
      <div ref={useFitToViewport()} className={tablet ? "tablet" : "kiosk-bare"}>
        <div className="kiosk">
          <TopBar orgName={org.name} currentTime={timeStr} />
          <div className="main" style={{ justifyContent: "center" }}>{screen}</div>
          <BottomBar
            stage={stage}
            onCancel={reset} />

          {loading && <LoadingVeil message={loading} />}
        </div>
      </div>

      {/* Botón flotante pantalla completa — protegido por PIN */}
      {!isDemo && <AdminAccessButton />}

      {!isDemo && <DemoTip visible={t.showHelper && showHelper} onHide={() => setShowHelper(false)} />}

      {!isDemo && (
      <TweaksPanel title="Tweaks">
        <TweakSection label={tt('tweak.identity')}>
          <TweakText label={tt('tweak.name')} value={t.orgName} onChange={(v) => setTweak('orgName', v)} />
          <TweakText label={tt('tweak.location')} value={t.orgLocation} onChange={(v) => setTweak('orgLocation', v)} />
        </TweakSection>
        <TweakSection label={tt('tweak.theme')}>
          <TweakColor label={tt('tweak.primary')}
          value={t.primaryColor}
          options={['#1E90FF', '#0EA5A5', '#7B5BFF', '#E94560', '#10B981']}
          onChange={(v) => setTweak('primaryColor', v)} />
          <TweakColor label={tt('tweak.accent')}
          value={t.accentColor}
          options={['#5DD3FF', '#7BE5C0', '#A98BFF', '#FFB86B', '#A0D0FF']}
          onChange={(v) => setTweak('accentColor', v)} />
        </TweakSection>
        <TweakSection label={tt('tweak.view')}>
          <TweakToggle label={tt('tweak.frame')} value={t.showFrame} onChange={(v) => setTweak('showFrame', v)} />
          <TweakToggle label={tt('tweak.demoIds')} value={t.showHelper} onChange={(v) => setTweak('showHelper', v)} />
        </TweakSection>
        <TweakSection label={tt('tweak.jumps')}>
          <div style={{ display: 'grid', gap: 8 }}>
            <SkipBtn onClick={() => {setFlow('appointment');setDni('30123456');setPatient(MOCK_DB.patients['30123456']);setStage('confirm');}}>
              {tt('tweak.jump.confirm')}
            </SkipBtn>
            <SkipBtn onClick={async () => {
              setFlow('appointment');setDni('30123456');
              const p = MOCK_DB.patients['30123456'];setPatient(p);
              setAppointments(MOCK_DB.appointments[p.id]);setStage('appts');
            }}>
              {tt('tweak.jump.appts')}
            </SkipBtn>
            <SkipBtn onClick={() => {setFlow('walkin');setDni('33456789');setPatient(MOCK_DB.patients['33456789']);setStage('reason');}}>
              {tt('tweak.jump.walkin')}
            </SkipBtn>
            <SkipBtn onClick={() => {
              setFlow('labs');setDni('28111222');
              const p = MOCK_DB.patients['28111222'];setPatient(p);
              setLabOrders(MOCK_DB.labOrders[p.id]);setStage('labs');
            }}>
              {tt('tweak.jump.labs')}
            </SkipBtn>
            <SkipBtn onClick={() => {
              const p = MOCK_DB.patients['30123456'];
              const a = MOCK_DB.appointments[p.id][0];
              setFlow('appointment');setDni('30123456');setPatient(p);
              setAppointment(a);setAppointments(MOCK_DB.appointments[p.id]);
              setTicketData({ ticketNumber: 'T-047', queuePosition: 3, eta: 8 });
              setStage('ticket');
            }}>
              {tt('tweak.jump.ticket')}
            </SkipBtn>
            <SkipBtn onClick={reset}>{tt('tweak.jump.idle')}</SkipBtn>
          </div>
        </TweakSection>
      </TweaksPanel>
      )}

      {/* Ticket de impresión — renderizado como portal en body para que @media print lo alcance */}
      {ticketData && ReactDOM.createPortal(
        <div id="print-ticket">
          <TicketContent
            org={org}
            flow={flow}
            patient={patient}
            appointment={appointment}
            reason={reason}
            coverage={coverage}
            labs={selectedLabs}
            ticketNumber={ticketData.ticketNumber}
            queuePosition={ticketData.queuePosition} />
        </div>,
        document.body
      )}
    </div>);

}

function SkipBtn({ children, onClick }) {
  return (
    <button onClick={onClick} style={{
      appearance: 'none',
      width: '100%',
      padding: '8px 12px',
      background: 'rgba(30,144,255,0.06)',
      border: '1px solid rgba(30,144,255,0.18)',
      borderRadius: 8,
      color: '#BFE0FF',
      fontSize: 12,
      fontFamily: 'inherit',
      cursor: 'pointer',
      textAlign: 'left',
      transition: 'all 0.15s'
    }}
    onMouseEnter={(e) => e.currentTarget.style.background = 'rgba(30,144,255,0.14)'}
    onMouseLeave={(e) => e.currentTarget.style.background = 'rgba(30,144,255,0.06)'}>
      → {children}</button>);

}

// "No results" reusable view
function NoneFound({ title, message, primary, secondary }) {
  return (
    <div className="screen scale-in">
      <div className="screen-inner" style={{ paddingTop: 0, justifyContent: 'center', display: 'flex', flexDirection: 'column' }}>
        <div className="nf-card" style={{
          background: 'rgba(255,181,71,0.04)',
          borderColor: 'rgba(255,181,71,0.2)',
          maxWidth: 640, margin: '0 auto',
          textAlign: 'center', padding: '32px 36px',
          border: '1px solid', borderRadius: 22
        }}>
          <div style={{
            width: 72, height: 72, margin: '0 auto 16px',
            display: 'grid', placeItems: 'center',
            borderRadius: '50%',
            background: 'rgba(255,181,71,0.08)',
            color: '#FFB547'
          }}>
            <Icon name="info" size={36} stroke={1.5} />
          </div>
          <h2 className="h-section" style={{ margin: 0 }}>{title}</h2>
          <p className="h-body" style={{ marginTop: 10, fontSize: 16 }}>{message}</p>
          <div style={{ marginTop: 22, display: 'flex', gap: 12, justifyContent: 'center' }}>
            {primary && <button className="btn btn-primary" style={{ height: 56, fontSize: 18 }} onClick={primary.onClick}>{primary.label}</button>}
            {secondary && <button className="btn btn-secondary" style={{ height: 56, fontSize: 18 }} onClick={secondary.onClick}>{secondary.label}</button>}
          </div>
        </div>
      </div>
    </div>);

}

// ---------- Hook: scale tablet to viewport ----------
function useFitToViewport() {
  const ref = useRef(null);
  useEffect(() => {
    const KIOSK_W = 1280;
    const KIOSK_H = 800;

    function fit() {
      const el = ref.current;
      if (!el) return;
      const vw = window.innerWidth;
      const vh = window.innerHeight;
      const margin = 0; // sin margen en mobile para maximizar espacio
      const s = Math.min(
        (vw - margin * 2) / KIOSK_W,
        (vh - margin * 2) / KIOSK_H
      );
      el.style.transform = `scale(${s})`;
      el.style.transformOrigin = 'top left';
      // Centrar el elemento escalado
      const scaledW = KIOSK_W * s;
      const scaledH = KIOSK_H * s;
      el.style.marginLeft = `${(vw - scaledW) / 2}px`;
      el.style.marginTop  = `${(vh - scaledH) / 2}px`;
    }

    // Usar requestAnimationFrame para asegurar que el DOM esté listo
    const raf = requestAnimationFrame(() => { fit(); });
    window.addEventListener('resize', fit);
    window.addEventListener('orientationchange', () => setTimeout(fit, 200));
    return () => {
      cancelAnimationFrame(raf);
      window.removeEventListener('resize', fit);
    };
  }, []);
  return ref;
}

// ---------- Color helper ----------
function shade(hex, percent) {
  const c = hex.replace('#', '');
  const n = c.length === 3 ?
  c.split('').map((x) => parseInt(x + x, 16)) :
  [parseInt(c.slice(0, 2), 16), parseInt(c.slice(2, 4), 16), parseInt(c.slice(4, 6), 16)];
  const adj = (v) => Math.max(0, Math.min(255, Math.round(v + percent / 100 * 255)));
  return '#' + n.map(adj).map((v) => v.toString(16).padStart(2, '0')).join('');
}

ReactDOM.createRoot(document.getElementById('app')).render(<App />);