// V7 — Live Call Hero. Real-time chat + push-to-talk with the Schedy agent.
//
// Picks a use case (Prenotazione / Spostamento / Cancellazione / Info) +
// vertical (8 hardcoded backend personalities) and posts every user turn to
// /engine/demo-chat on the demo tenant. The agent runs in `demoMode`: no DB
// writes, no GCal, no email — pure simulation. SidePanel renders the captures
// extracted from the agent's tool calls (BOOKING / RESCHEDULE / CANCEL / INFO).
//
// Backend URL is the demo tenant on schedy.app; CORS allowlist on the engine
// is scoped to this origin. Rate-limit 5/IP/day + global daily budget kill
// switch protect against abuse.

const DEMO_BACKEND_URL = 'https://studio-dentistico-multi-ruolo.schedy.app';

function persistedSessionId() {
  try {
    let id = sessionStorage.getItem('hello_demo_session');
    if (!id) {
      id = 'demo-' + (crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36).slice(2, 14));
      sessionStorage.setItem('hello_demo_session', id);
    }
    return id;
  } catch {
    return 'demo-' + Math.random().toString(36).slice(2, 14);
  }
}

// ───────────────────────────────────────────────────────────────────────────
// USE CASES + SCRIPTS

const USE_CASES = {
  it: [
    { id: 'prenotazione',  label: 'Prenotazione',   sub: 'Nuovo appuntamento' },
    { id: 'spostamento',   label: 'Spostamento',    sub: 'Sposta un appuntamento' },
    { id: 'cancellazione', label: 'Cancellazione',  sub: 'Cancella un appuntamento' },
    { id: 'informazione',  label: 'Informazioni',   sub: 'Prezzi, orari, servizi' },
  ],
  en: [
    { id: 'prenotazione',  label: 'Booking',        sub: 'New appointment' },
    { id: 'spostamento',   label: 'Reschedule',     sub: 'Move an appointment' },
    { id: 'cancellazione', label: 'Cancellation',   sub: 'Cancel an appointment' },
    { id: 'informazione',  label: 'Info request',   sub: 'Prices, hours, services' },
  ],
};

// Vertical-aware scripts. Each function returns:
//   { tool, fields, turns: [{at, role, text, captures?}] }
//
// `tool` is the captured tool label (BOOKING, RESCHEDULE, CANCEL, INFO).
// `fields` is the ordered list of capture keys shown in the side panel.
// Turns are no longer scripted — the live backend drives the conversation.

function buildScripts(v, lang) {
  if (lang === 'en') {
    return {
      prenotazione:  { tool: 'BOOKING',     fields: ['Customer', 'Service', 'Date', 'Time', 'Channel'] },
      spostamento:   { tool: 'RESCHEDULE',  fields: ['Customer', 'From', 'To', 'Service', 'Status'] },
      cancellazione: { tool: 'CANCEL',      fields: ['Customer', 'Appointment', 'Reason', 'Status', 'Slot freed'] },
      informazione:  { tool: 'INFO',        fields: ['Customer', 'Question', 'Answer', 'Follow-up', 'Outcome'] },
    };
  }
  return {
    prenotazione:  { tool: 'BOOKING',     fields: ['Cliente', 'Servizio', 'Data', 'Ora', 'Canale'] },
    spostamento:   { tool: 'RESCHEDULE',  fields: ['Cliente', 'Da', 'A', 'Servizio', 'Stato'] },
    cancellazione: { tool: 'CANCEL',      fields: ['Cliente', 'Appuntamento', 'Motivo', 'Stato', 'Slot liberato'] },
    informazione:  { tool: 'INFO',        fields: ['Cliente', 'Richiesta', 'Risposta', 'Follow-up', 'Esito'] },
  };
}

// Dead-code path kept for diff readability — fully replaced by buildScripts() above.
function _legacyScriptedTurns(v, lang) {
  const place    = v.place;
  const staff    = v.staffName;
  const service  = v.firstService.toLowerCase();
  const niche    = v.name.toLowerCase();
  const exampleClient = v.firstName;

  // The voice transcript that already exists per vertical drives the
  // "Spostamento" script — keeps prose feeling on-brand.
  const reschedFrom = v.voice.parsed.from || 'ven 15:00';
  const reschedTo   = v.voice.parsed.to   || 'sab 17:30';

  if (lang === 'en') {
    return {
      prenotazione: enBookingScript(v),
      spostamento:  enReschedScript(v, reschedFrom, reschedTo),
      cancellazione: enCancelScript(v),
      informazione: enInfoScript(v),
    };
  }

  return {
    prenotazione: {
      tool: 'BOOKING',
      fields: ['Cliente', 'Servizio', 'Data', 'Ora', 'Canale'],
      turns: [
        { at: 0.4,  role: 'agent', text: `Ciao! Sono Schedy, l'assistente di ${niche} — ${place}. Come posso aiutarti?` },
        { at: 4.6,  role: 'user',  text: 'Buongiorno, volevo prendere un appuntamento per venerdì pomeriggio.' },
        { at: 8.4,  role: 'agent', text: 'Volentieri. Per quale servizio?' },
        { at: 10.6, role: 'user',  text: `Un ${service}.`, captures: { Servizio: v.firstService } },
        { at: 13.4, role: 'agent', text: `Perfetto. Venerdì ho libero alle 15:30 oppure alle 17:00, sempre con ${staff}.` },
        { at: 18.0, role: 'user',  text: 'Alle 17 va benissimo.', captures: { Data: 'ven 22 nov', Ora: '17:00' } },
        { at: 20.8, role: 'agent', text: 'A che nome lo prenoto?' },
        { at: 22.6, role: 'user',  text: 'Sara Bianchi.', captures: { Cliente: 'Sara Bianchi' } },
        { at: 25.0, role: 'agent', text: 'Ti mando la conferma su WhatsApp tra un secondo, Sara. Vuoi anche un reminder un\'ora prima?' },
        { at: 30.2, role: 'user',  text: 'Sì grazie.', captures: { Canale: 'WhatsApp + reminder -1h' } },
        { at: 32.4, role: 'agent', text: 'Fatto. A venerdì alle 17. Buona giornata!' },
      ],
    },

    spostamento: {
      tool: 'RESCHEDULE',
      fields: ['Cliente', 'Da', 'A', 'Servizio', 'Stato'],
      turns: [
        { at: 0.4,  role: 'agent', text: `Ciao! Sono Schedy. Dimmi pure.` },
        { at: 3.2,  role: 'user',  text: v.voice.transcript, captures: { Cliente: v.voice.parsed.who, Servizio: v.firstService } },
        { at: 10.4, role: 'agent', text: `Capito. Vedo l'appuntamento di ${reschedFrom}. Lo sposto a ${reschedTo}?`, captures: { Da: reschedFrom } },
        { at: 15.2, role: 'user',  text: 'Sì, perfetto.', captures: { A: reschedTo, Stato: 'da confermare' } },
        { at: 17.4, role: 'agent', text: `Spostato. Ti arriva conferma su WhatsApp tra un attimo, e un promemoria un'ora prima.`, captures: { Stato: 'confermato ✓' } },
        { at: 22.6, role: 'user',  text: 'Grazie mille.' },
        { at: 24.2, role: 'agent', text: 'A te. Buona giornata!' },
      ],
    },

    cancellazione: {
      tool: 'CANCEL',
      fields: ['Cliente', 'Appuntamento', 'Motivo', 'Stato', 'Slot liberato'],
      turns: [
        { at: 0.4,  role: 'agent', text: `Ciao! Schedy in linea. Come posso aiutarti?` },
        { at: 3.4,  role: 'user',  text: 'Devo cancellare l\'appuntamento di domani, purtroppo.' },
        { at: 6.6,  role: 'agent', text: `Capisco. Posso avere il nome così lo trovo?` },
        { at: 9.4,  role: 'user',  text: `Sono ${exampleClient}.`, captures: { Cliente: exampleClient } },
        { at: 11.6, role: 'agent', text: `Trovato. ${exampleClient}, domani alle 10:30 — ${v.firstService.toLowerCase()}. Vuoi cancellare o riproporlo più avanti?`, captures: { Appuntamento: `domani 10:30 · ${v.firstService}` } },
        { at: 17.0, role: 'user',  text: 'Per ora solo cancellare, grazie. Mi è uscita una cosa al lavoro.', captures: { Motivo: 'imprevisto lavoro' } },
        { at: 21.0, role: 'agent', text: `Tranquillo, fatto. Lo slot torna libero. Se vuoi, ti scrivo io tra una settimana per riproporre un orario.`, captures: { Stato: 'cancellato ✓', 'Slot liberato': 'mar 10:30' } },
        { at: 27.4, role: 'user',  text: 'Sì grazie, perfetto.' },
        { at: 29.4, role: 'agent', text: 'A presto!' },
      ],
    },

    informazione: {
      tool: 'INFO',
      fields: ['Cliente', 'Richiesta', 'Risposta', 'Follow-up', 'Esito'],
      turns: [
        { at: 0.4,  role: 'agent', text: `Ciao! Sono Schedy, l'assistente di ${niche}. Dimmi pure.` },
        { at: 4.2,  role: 'user',  text: `Salve, volevo sapere quanto costa un ${service} e se avete posto in settimana.`, captures: { Richiesta: `prezzo + disponibilità ${v.firstService}` } },
        { at: 9.6,  role: 'agent', text: `Il ${service} da 60 minuti costa 65€. Questa settimana abbiamo libero mercoledì alle 11, giovedì alle 15:30 o venerdì alle 17.`, captures: { Risposta: '65€ · 60\' · 3 slot questa settimana' } },
        { at: 17.4, role: 'user',  text: 'Mercoledì alle 11 mi va benissimo. Mi chiamo Anna Rizzi.', captures: { Cliente: 'Anna Rizzi', 'Follow-up': 'mer 11:00 · prenotato' } },
        { at: 22.6, role: 'agent', text: `Perfetto Anna, ho bloccato il posto. Ti mando la conferma su WhatsApp e un reminder un'ora prima. A mercoledì!`, captures: { Esito: 'convertito → booking' } },
      ],
    },
  };
}

function enBookingScript(v) {
  return {
    tool: 'BOOKING',
    fields: ['Customer', 'Service', 'Date', 'Time', 'Channel'],
    turns: [
      { at: 0.4,  role: 'agent', text: `Hi! I'm Schedy, the assistant at ${v.name} — ${v.place}. How can I help?` },
      { at: 4.4,  role: 'user',  text: 'Hi, I\'d like to book an appointment for Friday afternoon.' },
      { at: 8.0,  role: 'agent', text: 'Sure! Which service?' },
      { at: 10.0, role: 'user',  text: `A ${v.firstService.toLowerCase()}.`, captures: { Service: v.firstService } },
      { at: 12.6, role: 'agent', text: `Got it. Friday I have 3:30pm or 5pm available with ${v.staffName}.` },
      { at: 17.2, role: 'user',  text: '5pm works great.', captures: { Date: 'Fri Nov 22', Time: '17:00' } },
      { at: 19.8, role: 'agent', text: 'And what name should I put it under?' },
      { at: 21.6, role: 'user',  text: 'Sara Bianchi.', captures: { Customer: 'Sara Bianchi' } },
      { at: 23.8, role: 'agent', text: 'I\'ll text you the confirmation on WhatsApp in a second, Sara. Want a reminder an hour before too?' },
      { at: 29.0, role: 'user',  text: 'Yes please.', captures: { Channel: 'WhatsApp + 1h reminder' } },
      { at: 31.2, role: 'agent', text: 'Done. See you Friday at 5. Have a good one!' },
    ],
  };
}
function enReschedScript(v, from, to) {
  return {
    tool: 'RESCHEDULE',
    fields: ['Customer', 'From', 'To', 'Service', 'Status'],
    turns: [
      { at: 0.4,  role: 'agent', text: `Hi! Schedy here. What can I do for you?` },
      { at: 3.2,  role: 'user',  text: 'Hi, I need to move my appointment from Friday to Saturday afternoon, late if possible.', captures: { Customer: v.voice.parsed.who, Service: v.firstService } },
      { at: 9.6,  role: 'agent', text: `Okay — I see ${from}. Move it to ${to}?`, captures: { From: from } },
      { at: 14.4, role: 'user',  text: 'Yes, perfect.', captures: { To: to, Status: 'pending' } },
      { at: 16.6, role: 'agent', text: `Moved. You'll get the confirmation on WhatsApp shortly, plus a reminder one hour before.`, captures: { Status: 'confirmed ✓' } },
      { at: 21.4, role: 'user',  text: 'Thanks a lot.' },
      { at: 23.0, role: 'agent', text: 'Anytime. Have a great one!' },
    ],
  };
}
function enCancelScript(v) {
  return {
    tool: 'CANCEL',
    fields: ['Customer', 'Appointment', 'Reason', 'Status', 'Slot freed'],
    turns: [
      { at: 0.4,  role: 'agent', text: 'Hi! Schedy speaking. How can I help?' },
      { at: 3.2,  role: 'user',  text: 'I need to cancel my appointment tomorrow.' },
      { at: 6.0,  role: 'agent', text: 'Got it. Can I have your name?' },
      { at: 8.4,  role: 'user',  text: `${v.firstName}.`, captures: { Customer: v.firstName } },
      { at: 10.6, role: 'agent', text: `Found you. ${v.firstName}, tomorrow at 10:30 — ${v.firstService.toLowerCase()}. Cancel, or move it?`, captures: { Appointment: `Tomorrow 10:30 · ${v.firstService}` } },
      { at: 16.0, role: 'user',  text: 'Just cancel for now, work emergency.', captures: { Reason: 'work emergency' } },
      { at: 20.0, role: 'agent', text: `No worries — done. The slot's open again. Want me to reach out next week to find a new time?`, captures: { Status: 'cancelled ✓', 'Slot freed': 'Tue 10:30' } },
      { at: 26.2, role: 'user',  text: 'Yes, that would be great.' },
      { at: 28.4, role: 'agent', text: 'Talk soon!' },
    ],
  };
}
function enInfoScript(v) {
  return {
    tool: 'INFO',
    fields: ['Customer', 'Question', 'Answer', 'Follow-up', 'Outcome'],
    turns: [
      { at: 0.4,  role: 'agent', text: `Hi! I'm Schedy, the assistant at ${v.name}. What can I do for you?` },
      { at: 4.4,  role: 'user',  text: `Hi, how much is a ${v.firstService.toLowerCase()} and do you have any slots this week?`, captures: { Question: `Price + availability ${v.firstService}` } },
      { at: 9.6,  role: 'agent', text: `60 minutes is €65. This week I have Wed 11am, Thu 3:30pm, or Fri 5pm.`, captures: { Answer: '€65 · 60\' · 3 slots' } },
      { at: 16.8, role: 'user',  text: 'Wed 11am works. My name is Anna Rizzi.', captures: { Customer: 'Anna Rizzi', 'Follow-up': 'Wed 11:00 · booked' } },
      { at: 21.8, role: 'agent', text: `Done, Anna. Confirmation on WhatsApp, reminder an hour before. See you Wednesday!`, captures: { Outcome: 'converted → booking' } },
    ],
  };
}

// ───────────────────────────────────────────────────────────────────────────
// LIVE WAVEFORM — animated bars when active, flat dots when idle.

function LiveWaveform({ active, speaker }) {
  const T = useTheme();
  const N = 22;
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 3, height: 28 }}>
      {Array.from({ length: N }).map((_, i) => {
        const baseH = 3 + ((i * 9) % 18);
        const animH = 6 + ((i * 13) % 22);
        return (
          <span key={i} style={{
            display: 'inline-block', width: 2.5, borderRadius: 2,
            background: active
              ? (speaker === 'agent' ? T.corallo : T.viola)
              : T.line2,
            height: active ? `${animH}px` : `${baseH}px`,
            opacity: active ? 0.9 : 0.55,
            transformOrigin: 'center',
            animation: active ? `lcWave ${0.5 + (i % 6) * 0.08}s ease-in-out ${(i % 10) * 0.04}s infinite alternate` : 'none',
          }} />
        );
      })}
      <style>{`
        @keyframes lcWave {
          0%   { transform: scaleY(0.35); }
          100% { transform: scaleY(1); }
        }
      `}</style>
    </div>
  );
}

// ───────────────────────────────────────────────────────────────────────────
// USE CASE PICKER — segmented pill grid, editorial.

function UseCasePicker({ cases, value, onChange, vertical, lang }) {
  const T = useTheme();
  const { isMobile } = useViewport();
  return (
    <div>
      <div style={{
        fontFamily: T.mono, fontSize: 10, color: T.faint, letterSpacing: 2, fontWeight: 600,
        marginBottom: 12,
      }}>
        {lang === 'en' ? '01 — PICK A USE CASE' : '01 — SCEGLI UN CASO D\'USO'}
      </div>
      <div style={{
        display: 'grid',
        gridTemplateColumns: isMobile ? '1fr 1fr' : 'repeat(4, 1fr)',
        gap: 8,
      }}>
        {cases.map((c) => {
          const sel = c.id === value;
          return (
            <button key={c.id} onClick={() => onChange(c.id)} style={{
              textAlign: 'left', cursor: 'pointer',
              padding: '12px 14px', borderRadius: 12,
              background: sel ? T.text : T.ink2,
              color: sel ? T.ink : T.text,
              border: `1px solid ${sel ? T.text : T.paperLine}`,
              fontFamily: T.font,
              transition: 'background .15s, color .15s, border-color .15s',
              display: 'flex', flexDirection: 'column', gap: 3,
            }}>
              <span style={{ fontSize: 14, fontWeight: 700, letterSpacing: -0.2 }}>{c.label}</span>
              <span style={{ fontSize: 11, opacity: sel ? 0.75 : 0.55, fontWeight: 400 }}>{c.sub}</span>
            </button>
          );
        })}
      </div>
    </div>
  );
}

// ───────────────────────────────────────────────────────────────────────────
// IDLE PREVIEW — what the panel looks like before Avvia.

function IdlePreview({ vertical, useCaseLabel, lang }) {
  const T = useTheme();
  const { isMobile } = useViewport();
  return (
    <div style={{
      background: T.ink2,
      border: `1px solid ${T.paperLine}`,
      borderRadius: 18,
      padding: isMobile ? 20 : 28,
      position: 'relative',
      boxShadow: T.mode === 'light' ? '0 24px 50px rgba(20,15,30,0.07)' : '0 24px 50px rgba(0,0,0,0.3)',
      minHeight: 380,
      display: 'flex', flexDirection: 'column', gap: 18,
    }}>
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
          <span style={{
            width: 10, height: 10, borderRadius: 5, background: T.line2,
            boxShadow: 'inset 0 0 0 2px ' + T.ink2,
          }} />
          <span style={{ fontFamily: T.mono, fontSize: 11, color: T.muted, letterSpacing: 2, fontWeight: 600 }}>
            {lang === 'en' ? 'STAND-BY' : 'STAND-BY'}
          </span>
        </div>
        <div style={{ fontFamily: T.mono, fontSize: 10, color: T.faint, letterSpacing: 1.5 }}>00:00</div>
      </div>

      <div style={{
        flex: 1, display: 'flex', flexDirection: 'column',
        alignItems: 'center', justifyContent: 'center', gap: 18,
        textAlign: 'center', padding: '20px 8px',
      }}>
        <div style={{
          width: 76, height: 76, borderRadius: 38,
          background: T.ink3, border: `1px solid ${T.paperLine}`,
          display: 'flex', alignItems: 'center', justifyContent: 'center',
          color: T.muted,
        }}>
          <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
            <path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72c.13.96.37 1.9.72 2.81a2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45c.91.35 1.85.59 2.81.72A2 2 0 0 1 22 16.92z"/>
          </svg>
        </div>
        <div style={{
          fontSize: 17, fontWeight: 700, color: T.text, letterSpacing: -0.3,
          maxWidth: 280, lineHeight: 1.3,
        }}>
          {lang === 'en'
            ? <>Tap <em style={{ color: T.corallo, fontStyle: 'italic' }}>Start call</em> to hear how Schedy handles{' '}<br/>{useCaseLabel.toLowerCase()}.</>
            : <>Premi <em style={{ color: T.corallo, fontStyle: 'italic' }}>Avvia chiamata</em> per ascoltare come Schedy gestisce {' '}<br/>{useCaseLabel.toLowerCase()}.</>}
        </div>
        <div style={{ fontSize: 12, color: T.faint, fontFamily: T.mono, letterSpacing: 1.5 }}>
          {vertical.name.toUpperCase()} · {vertical.place.toUpperCase()}
        </div>
      </div>

      <div style={{
        display: 'flex', alignItems: 'center', justifyContent: 'space-between',
        padding: '12px 14px',
        background: T.ink3, border: `1px solid ${T.paperLine}`, borderRadius: 10,
      }}>
        <div style={{ fontSize: 11, color: T.faint, fontFamily: T.mono, letterSpacing: 1.5 }}>
          {lang === 'en' ? 'CHANNELS' : 'CANALI'}
        </div>
        <div style={{ display: 'flex', gap: 14, fontSize: 11, color: T.muted, fontWeight: 500 }}>
          <span>Voice</span>
          <span>·</span>
          <span>WhatsApp</span>
          <span>·</span>
          <span>SMS</span>
        </div>
      </div>
    </div>
  );
}

// ───────────────────────────────────────────────────────────────────────────
// ACTIVE CALL — bubbles streaming, waveform, text input.

/**
 * Client-side mock of the 6 demo tools — mirrors mockToolCall in
 * docker/booking-engine/src/agent/states/tool-execution.ts. gpt-realtime calls
 * the tool over the data channel; we respond with shape-compatible fake
 * results so the conversation continues without any DB / GCal / email writes.
 */
function mockRealtimeTool(name, args) {
  switch (name) {
    case 'verifica_disponibilita': {
      const date = args.date || new Date().toISOString().slice(0, 10);
      return { success: true, slots: [
        { date, slot_start: '10:00', slot_end: '11:00' },
        { date, slot_start: '11:30', slot_end: '12:30' },
        { date, slot_start: '15:00', slot_end: '16:00' },
        { date, slot_start: '17:00', slot_end: '18:00' },
      ]};
    }
    case 'crea_prenotazione':
      return { success: true, booking_id: 'demo-bk-' + Math.random().toString(36).slice(2, 10),
        message: `Prenotazione confermata per ${args.client_name} il ${args.date} alle ${args.start_time}.` };
    case 'verifica_appuntamenti':
      return { success: true, appointments: [] };
    case 'modifica_appuntamento':
      return { success: true, message: args.action === 'cancel' ? 'Appuntamento cancellato.' : 'Appuntamento spostato.' };
    case 'registra_cliente':
      return { success: true, client_id: 'demo-cl-' + Math.random().toString(36).slice(2, 10) };
    case 'notifica_escalation':
      return { success: true, message: 'Operatore notificato.' };
    default:
      return { success: false, error: 'Unknown tool: ' + name };
  }
}

/**
 * Voice-Live (WebRTC) body. Opens a direct peer connection to OpenAI Realtime
 * using an ephemeral token we fetch from /engine/realtime-session. Handles:
 *   - mic capture + audio output element
 *   - data channel for transcripts, tool calls, status events
 *   - tool-call dispatch via mockRealtimeTool (client-side, no engine roundtrip)
 *   - 5-min hard cap + Termina
 */
function RealtimeBody({ vertical, lang, voice, onEnd, onCapture, onMeta, sessionId }) {
  const T = useTheme();
  const { isMobile } = useViewport();
  const [status, setStatus] = React.useState('connecting'); // connecting|live|ended|error
  const [errorMsg, setErrorMsg] = React.useState('');
  const [transcript, setTranscript] = React.useState([]); // {role:'user'|'assistant', text, id}
  const [tick, setTick] = React.useState(0);
  const [maxSec, setMaxSec] = React.useState(300);
  const pcRef = React.useRef(null);
  const dcRef = React.useRef(null);
  const audioRef = React.useRef(null);
  const startRef = React.useRef(Date.now());
  const turnIdxRef = React.useRef(0);
  const liveAssistantRef = React.useRef(null); // streaming accumulator for partial assistant text

  React.useEffect(() => {
    const id = setInterval(() => setTick(Math.floor((Date.now() - startRef.current) / 1000)), 250);
    return () => clearInterval(id);
  }, []);

  // Auto-end at max session length.
  React.useEffect(() => {
    if (status === 'live' && tick >= maxSec) {
      hangUp();
    }
  }, [tick, maxSec, status]);

  React.useEffect(() => {
    let cancelled = false;
    (async () => {
      try {
        // 1. Get ephemeral token + session config.
        const r = await fetch(`${DEMO_BACKEND_URL}/engine/realtime-session`, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ vertical_id: vertical.id, lang, voice, session_id: sessionId }),
        });
        const data = await r.json();
        if (!data.success) {
          setErrorMsg(data.error || (lang === 'en' ? 'Could not open voice session.' : 'Impossibile aprire la sessione voce.'));
          setStatus('error');
          return;
        }
        if (cancelled) return;
        if (typeof data.max_seconds === 'number') setMaxSec(data.max_seconds);
        if (onMeta) onMeta({ model: data.model, voice: data.voice, latency: null, remaining: data.remaining_calls });

        // 2. Mic capture.
        const ms = await navigator.mediaDevices.getUserMedia({ audio: true });
        if (cancelled) { ms.getTracks().forEach(t => t.stop()); return; }

        // 3. RTCPeerConnection — direct to OpenAI.
        const pc = new RTCPeerConnection();
        pcRef.current = pc;
        pc.ontrack = (e) => {
          if (audioRef.current) audioRef.current.srcObject = e.streams[0];
        };
        pc.addTrack(ms.getTracks()[0], ms);

        const dc = pc.createDataChannel('oai-events');
        dcRef.current = dc;
        dc.onopen = () => setStatus('live');
        dc.onmessage = (e) => handleRealtimeEvent(JSON.parse(e.data));

        // 4. SDP exchange.
        const offer = await pc.createOffer();
        await pc.setLocalDescription(offer);
        // GA endpoint (2026-05-15): /v1/realtime/calls replaces beta /v1/realtime.
        // OpenAI-Beta header no longer needed.
        const sdpRes = await fetch(`https://api.openai.com/v1/realtime/calls?model=${encodeURIComponent(data.model || 'gpt-realtime')}`, {
          method: 'POST',
          body: offer.sdp,
          headers: {
            'Authorization': `Bearer ${data.client_secret}`,
            'Content-Type': 'application/sdp',
          },
        });
        if (!sdpRes.ok) {
          throw new Error(`SDP exchange ${sdpRes.status}`);
        }
        const answerSdp = await sdpRes.text();
        await pc.setRemoteDescription({ type: 'answer', sdp: answerSdp });
        startRef.current = Date.now();
      } catch (err) {
        console.error('realtime init error:', err);
        if (!cancelled) {
          setErrorMsg(lang === 'en' ? 'Voice connection failed.' : 'Connessione voce fallita.');
          setStatus('error');
        }
      }
    })();
    return () => { cancelled = true; hangUp(true); };
    // eslint-disable-next-line
  }, []);

  function handleRealtimeEvent(evt) {
    // GA event names (2026-05-15): response.audio_transcript.* → response.output_audio_transcript.*.
    // Keep beta names as fallback for any cached/transitional clients.
    if (evt.type === 'response.output_audio_transcript.delta' || evt.type === 'response.audio_transcript.delta') {
      if (!liveAssistantRef.current) {
        const id = 't' + (turnIdxRef.current++);
        liveAssistantRef.current = id;
        setTranscript(v => [...v, { role: 'assistant', text: evt.delta || '', id }]);
      } else {
        const id = liveAssistantRef.current;
        setTranscript(v => v.map(m => m.id === id ? { ...m, text: m.text + (evt.delta || '') } : m));
      }
      return;
    }
    if (evt.type === 'response.output_audio_transcript.done'
      || evt.type === 'response.audio_transcript.done'
      || evt.type === 'response.done') {
      liveAssistantRef.current = null;
      return;
    }
    // User audio transcription (whisper sidecar).
    if (evt.type === 'conversation.item.input_audio_transcription.completed') {
      const text = evt.transcript || '';
      if (text.trim()) {
        setTranscript(v => [...v, { role: 'user', text, id: 't' + (turnIdxRef.current++) }]);
      }
      return;
    }
    // Tool call dispatched by the model.
    if (evt.type === 'response.function_call_arguments.done') {
      let args = {};
      try { args = JSON.parse(evt.arguments || '{}'); } catch {}
      const result = mockRealtimeTool(evt.name, args);
      if (onCapture) {
        const cap = {};
        if (args.client_name) cap[lang === 'en' ? 'Customer' : 'Cliente'] = args.client_name;
        if (args.service_name) cap[lang === 'en' ? 'Service' : 'Servizio'] = args.service_name;
        if (args.date) cap[lang === 'en' ? 'Date' : 'Data'] = args.date;
        if (args.start_time) cap[lang === 'en' ? 'Time' : 'Ora'] = args.start_time;
        if (Object.keys(cap).length) onCapture(cap);
      }
      const dc = dcRef.current;
      if (dc && dc.readyState === 'open') {
        dc.send(JSON.stringify({
          type: 'conversation.item.create',
          item: { type: 'function_call_output', call_id: evt.call_id, output: JSON.stringify(result) },
        }));
        dc.send(JSON.stringify({ type: 'response.create' }));
      }
      return;
    }
    if (evt.type === 'error') {
      console.error('OpenAI Realtime error event:', evt);
      setErrorMsg((evt.error && evt.error.message) || 'Realtime error');
      setStatus('error');
    }
  }

  function hangUp(silent) {
    try { dcRef.current?.close(); } catch {}
    try {
      pcRef.current?.getSenders().forEach(s => s.track && s.track.stop());
      pcRef.current?.close();
    } catch {}
    pcRef.current = null;
    dcRef.current = null;
    if (!silent) setStatus('ended');
  }

  function terminate() {
    hangUp();
    if (onEnd) onEnd();
  }

  const m = Math.floor(tick / 60), s = tick % 60;
  const timeStr = `${String(m).padStart(2,'0')}:${String(s).padStart(2,'0')}`;
  const remaining = Math.max(0, maxSec - tick);
  const remMin = Math.floor(remaining / 60), remSec = remaining % 60;

  return (
    <div style={{
      background: T.ink2, border: `1px solid ${T.paperLine}`, borderRadius: 18,
      overflow: 'hidden', position: 'relative', height: '100%',
      display: 'grid', gridTemplateRows: 'auto 1fr auto', minHeight: 0,
      boxShadow: T.mode === 'light' ? '0 24px 50px rgba(20,15,30,0.10)' : '0 24px 50px rgba(0,0,0,0.4)',
    }}>
      {/* Top bar */}
      <div style={{
        display: 'flex', alignItems: 'center', justifyContent: 'space-between',
        padding: '14px 18px', borderBottom: `1px solid ${T.paperLine}`,
      }}>
        <button onClick={terminate} style={{
          background: 'transparent', border: 'none', cursor: 'pointer',
          color: T.muted, fontFamily: T.font, fontSize: 13, fontWeight: 500,
          display: 'inline-flex', alignItems: 'center', gap: 6, padding: 0,
        }}>
          <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M19 12H5M12 19l-7-7 7-7"/></svg>
          {lang === 'en' ? 'End' : 'Termina'}
        </button>
        <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
          <span style={{
            width: 8, height: 8, borderRadius: 4,
            background: status === 'live' ? T.corallo : (status === 'error' ? '#dc2626' : T.muted),
            boxShadow: status === 'live' ? `0 0 0 0 ${T.corallo}` : 'none',
            animation: status === 'live' ? 'lcPulse 1.4s ease-in-out infinite' : 'none',
          }} />
          <span style={{ fontFamily: T.mono, fontSize: 11, color: T.text, letterSpacing: 2, fontWeight: 700 }}>
            {status === 'connecting' && (lang === 'en' ? 'CONNECTING' : 'CONNESSIONE')}
            {status === 'live' && 'LIVE'}
            {status === 'ended' && (lang === 'en' ? 'ENDED' : 'TERMINATA')}
            {status === 'error' && (lang === 'en' ? 'ERROR' : 'ERRORE')}
          </span>
          <span style={{ width: 1, height: 14, background: T.paperLine }} />
          <span style={{ fontFamily: T.mono, fontSize: 13, color: T.text, fontVariantNumeric: 'tabular-nums', letterSpacing: 1 }}>{timeStr}</span>
        </div>
        <span style={{ fontFamily: T.mono, fontSize: 10, color: T.faint, letterSpacing: 1 }}>
          {status === 'live' ? `−${String(remMin).padStart(2,'0')}:${String(remSec).padStart(2,'0')}` : ''}
        </span>
      </div>

      {/* Center — transcript + live waveform indicator */}
      <div style={{
        padding: isMobile ? '20px 16px' : '28px 32px',
        overflowY: 'auto', minHeight: 0,
        display: 'flex', flexDirection: 'column', gap: 12,
      }}>
        {status === 'connecting' && (
          <div style={{ color: T.faint, fontSize: 13, fontStyle: 'italic', textAlign: 'center', padding: '40px 0' }}>
            {lang === 'en' ? 'Connecting voice…' : 'Connessione voce…'}
          </div>
        )}
        {status === 'error' && (
          <div style={{ color: '#dc2626', fontSize: 13, textAlign: 'center', padding: '40px 0' }}>
            {errorMsg}
          </div>
        )}
        {transcript.map((t) => (
          <Bubble key={t.id} role={t.role === 'assistant' ? 'agent' : 'user'} text={t.text} vertical={vertical} lang={lang} />
        ))}
      </div>

      {/* Footer — mic indicator (live, no push-to-talk in voice mode) */}
      <div style={{
        display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 14,
        padding: '16px', borderTop: `1px solid ${T.paperLine}`,
      }}>
        <div style={{
          width: 44, height: 44, borderRadius: 22,
          background: status === 'live' ? T.corallo : T.ink3,
          color: status === 'live' ? '#fff' : T.muted,
          display: 'flex', alignItems: 'center', justifyContent: 'center',
          position: 'relative',
        }}>
          <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
            <rect x="9" y="2" width="6" height="12" rx="3"/>
            <path d="M5 10v2a7 7 0 0 0 14 0v-2"/>
            <line x1="12" y1="19" x2="12" y2="22"/>
          </svg>
          {status === 'live' && (
            <span style={{
              position: 'absolute', inset: -4, borderRadius: 26,
              border: `2px solid ${T.corallo}`, opacity: 0.7,
              animation: 'lcRing 1.6s ease-out infinite',
            }} />
          )}
        </div>
        <div style={{ fontFamily: T.mono, fontSize: 11, color: T.muted, letterSpacing: 1 }}>
          {status === 'live'
            ? (lang === 'en' ? 'Speak naturally — Schedy will answer' : 'Parla pure — Schedy risponde')
            : ''}
        </div>
      </div>

      <audio ref={audioRef} autoPlay playsInline />

      <style>{`
        @keyframes lcPulse {
          0% { box-shadow: 0 0 0 0 rgba(244,63,94,0.55); }
          70% { box-shadow: 0 0 0 8px rgba(244,63,94,0); }
          100% { box-shadow: 0 0 0 0 rgba(244,63,94,0); }
        }
        @keyframes lcRing {
          0% { transform: scale(1); opacity: 0.7; }
          100% { transform: scale(1.5); opacity: 0; }
        }
      `}</style>
    </div>
  );
}

function ActiveCall({ vertical, script, useCaseLabel, lang, mode, voice, onEnd, onReset, onCapture, onMeta, sessionId }) {
  // Voice-live (WebRTC) branch: completely different component lifecycle.
  if (mode === 'voice') {
    return <RealtimeBody
      vertical={vertical}
      lang={lang}
      voice={voice || 'cedar'}
      onEnd={onEnd}
      onCapture={onCapture}
      onMeta={onMeta}
      sessionId={sessionId}
    />;
  }

  const T = useTheme();
  const { isMobile } = useViewport();
  const [tick, setTick] = React.useState(0);
  const [visible, setVisible] = React.useState([]);
  const [userDraft, setUserDraft] = React.useState('');
  const [finished, setFinished] = React.useState(false);
  const [awaiting, setAwaiting] = React.useState(false);
  const [recording, setRecording] = React.useState(false);
  const scrollRef = React.useRef(null);
  const startRef = React.useRef(Date.now());
  const mediaRecRef = React.useRef(null);
  const chunksRef = React.useRef([]);
  const turnIdxRef = React.useRef(0);

  React.useEffect(() => {
    startRef.current = Date.now();
    const id = setInterval(() => setTick(Math.floor((Date.now() - startRef.current) / 100) / 10), 100);
    return () => clearInterval(id);
  }, []);

  React.useEffect(() => {
    if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
  }, [visible.length, awaiting]);

  async function sendMessage(textInput, audioBlob) {
    if (finished || awaiting) return;
    if (!textInput && !audioBlob) return;
    setAwaiting(true);
    // Optimistic user bubble for text. Audio path waits for the STT transcript
    // from the server before painting the customer bubble.
    if (textInput) {
      setVisible(v => [...v, { role: 'user', text: textInput, idx: 't' + (turnIdxRef.current++) }]);
    }
    try {
      let res;
      if (audioBlob) {
        const fd = new FormData();
        fd.append('vertical_id', vertical.id);
        fd.append('session_id', sessionId);
        fd.append('lang', lang);
        fd.append('audio', audioBlob, 'voice.webm');
        res = await fetch(`${DEMO_BACKEND_URL}/engine/demo-chat`, { method: 'POST', body: fd });
      } else {
        res = await fetch(`${DEMO_BACKEND_URL}/engine/demo-chat`, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ vertical_id: vertical.id, message: textInput, session_id: sessionId, lang }),
        });
      }
      const data = await res.json();
      if (data.transcript) {
        setVisible(v => [...v, { role: 'user', text: data.transcript, idx: 't' + (turnIdxRef.current++) }]);
      }
      if (data.success) {
        setVisible(v => [...v, { role: 'agent', text: data.reply, idx: 't' + (turnIdxRef.current++) }]);
        if (onCapture && data.captures && Object.keys(data.captures).length) {
          onCapture(data.captures);
        }
        if (onMeta) {
          onMeta({
            latency: data.latency_ms,
            model: data.model_used,
            voice: data.voice_engine,
            remaining: data.remaining_calls,
          });
        }
      } else {
        const errMsg = data.error || (lang === 'en' ? 'Connection error.' : 'Errore di connessione.');
        setVisible(v => [...v, { role: 'agent', text: errMsg, idx: 't' + (turnIdxRef.current++) }]);
        if (res.status === 429) setFinished(true);
      }
    } catch (err) {
      setVisible(v => [...v, {
        role: 'agent',
        text: lang === 'en' ? 'Connection failed. Retry?' : 'Connessione fallita. Riprova.',
        idx: 't' + (turnIdxRef.current++),
      }]);
    } finally {
      setAwaiting(false);
    }
  }

  function handleUserSubmit(e) {
    e.preventDefault();
    const text = userDraft.trim();
    if (!text) return;
    setUserDraft('');
    sendMessage(text, null);
  }

  async function startRecording() {
    if (finished || awaiting || recording) return;
    try {
      const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
      let mime = 'audio/webm;codecs=opus';
      if (!window.MediaRecorder || !MediaRecorder.isTypeSupported(mime)) mime = 'audio/webm';
      const mr = new MediaRecorder(stream, { mimeType: mime });
      chunksRef.current = [];
      mr.ondataavailable = (e) => { if (e.data.size > 0) chunksRef.current.push(e.data); };
      mr.onstop = () => {
        stream.getTracks().forEach(t => t.stop());
        const blob = new Blob(chunksRef.current, { type: 'audio/webm' });
        if (blob.size > 0) sendMessage(null, blob);
      };
      mr.start();
      mediaRecRef.current = mr;
      setRecording(true);
      // Safety: hard cap at 60s to mirror the backend audio limit (2MB).
      setTimeout(() => { if (mr.state === 'recording') mr.stop(); }, 60000);
    } catch (e) {
      const msg = lang === 'en' ? 'Microphone access denied.' : 'Accesso al microfono negato.';
      setVisible(v => [...v, { role: 'agent', text: msg, idx: 't' + (turnIdxRef.current++) }]);
    }
  }

  function stopRecording() {
    if (mediaRecRef.current && mediaRecRef.current.state === 'recording') {
      mediaRecRef.current.stop();
    }
    setRecording(false);
  }

  const m = Math.floor(tick / 60);
  const s = Math.floor(tick % 60);
  const timeStr = `${String(m).padStart(2,'0')}:${String(s).padStart(2,'0')}`;
  const speaker = awaiting ? 'agent' : (recording ? 'user' : null);

  return (
    <div style={{
      background: T.ink2,
      border: `1px solid ${T.paperLine}`,
      borderRadius: 18,
      overflow: 'hidden',
      position: 'relative',
      boxShadow: T.mode === 'light' ? '0 24px 50px rgba(20,15,30,0.10)' : '0 24px 50px rgba(0,0,0,0.4)',
      display: 'grid',
      gridTemplateRows: 'auto 1fr auto',
      // Fullscreen overlay parent supplies the height — fill it instead of
      // forcing a min that overflows small viewports (iPhone SE landscape etc).
      height: '100%',
      minHeight: 0,
    }}>
      {/* top bar */}
      <div style={{
        display: 'flex', alignItems: 'center', justifyContent: 'space-between',
        padding: '14px 18px', borderBottom: `1px solid ${T.paperLine}`,
        background: T.ink2,
      }}>
        <button onClick={onEnd} style={{
          background: 'transparent', border: 'none', cursor: 'pointer',
          color: T.muted, fontFamily: T.font, fontSize: 13, fontWeight: 500,
          display: 'inline-flex', alignItems: 'center', gap: 6, padding: 0,
        }}>
          <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M19 12H5M12 19l-7-7 7-7"/></svg>
          {lang === 'en' ? 'End' : 'Termina'}
        </button>

        <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
          <span style={{
            width: 8, height: 8, borderRadius: 4,
            background: finished ? T.muted : T.corallo,
            boxShadow: finished ? 'none' : `0 0 0 0 ${T.corallo}`,
            animation: finished ? 'none' : 'lcPulse 1.4s ease-in-out infinite',
          }} />
          <span style={{ fontFamily: T.mono, fontSize: 11, color: T.text, letterSpacing: 2, fontWeight: 700 }}>
            {finished ? (lang === 'en' ? 'ENDED' : 'TERMINATA') : 'LIVE'}
          </span>
          <span style={{ width: 1, height: 14, background: T.paperLine }} />
          <span style={{ fontFamily: T.mono, fontSize: 13, color: T.text, fontVariantNumeric: 'tabular-nums', letterSpacing: 1 }}>{timeStr}</span>
        </div>

        <button onClick={onReset} style={{
          background: 'transparent', border: `1px solid ${T.paperLine}`,
          cursor: 'pointer', color: T.muted, fontFamily: T.mono, fontSize: 10,
          letterSpacing: 1.5, padding: '5px 10px', borderRadius: 6, fontWeight: 600,
        }}>{lang === 'en' ? '↻ RESTART' : '↻ RIPETI'}</button>
      </div>

      {/* transcript — fills the middle 1fr row, scrolls internally. The fixed
          maxHeight (360/440) used to leave dead space on tall mobile screens
          and clip content on short ones. Letting the grid row drive the
          height gives a true fit-to-viewport in the fullscreen overlay. */}
      <div ref={scrollRef} style={{
        padding: isMobile ? '20px 16px' : '24px 28px',
        overflowY: 'auto',
        minHeight: 0,
        display: 'flex', flexDirection: 'column', gap: 12,
      }}>
        {visible.length === 0 && !awaiting && !recording && (
          <div style={{ color: T.faint, fontSize: 13, fontStyle: 'italic', textAlign: 'center', padding: '40px 0' }}>
            {lang === 'en'
              ? 'Type a message or hold the mic to talk.'
              : 'Scrivi un messaggio o tieni premuto il microfono per parlare.'}
          </div>
        )}
        {visible.map((turn) => (
          <Bubble key={turn.idx} role={turn.role} text={turn.text} vertical={vertical} lang={lang} />
        ))}
        {!finished && speaker && (
          <div style={{
            alignSelf: speaker === 'agent' ? 'flex-start' : 'flex-end',
            display: 'flex', alignItems: 'center', gap: 6,
            padding: '6px 10px',
            background: speaker === 'agent' ? T.ink3 : T.text,
            color: speaker === 'agent' ? T.muted : T.ink,
            borderRadius: 12, fontSize: 11, fontFamily: T.mono, letterSpacing: 1,
          }}>
            <Dots color={speaker === 'agent' ? T.muted : T.ink} />
            {speaker === 'agent'
              ? 'SCHEDY'
              : (lang === 'en' ? 'RECORDING…' : 'REGISTRAZIONE…')}
          </div>
        )}
        {finished && (
          <div style={{
            alignSelf: 'center', marginTop: 12,
            padding: '8px 16px', borderRadius: 999,
            background: T.violaSoft, border: `1px solid ${T.violaBorder}`,
            fontSize: 11, color: T.text, fontFamily: T.mono, letterSpacing: 1.5, fontWeight: 600,
          }}>
            ✓ {lang === 'en' ? 'CALL ENDED' : 'CHIAMATA TERMINATA'} · {script.tool} {lang === 'en' ? 'SAVED' : 'SALVATO'}
          </div>
        )}
      </div>

      {/* footer: mic + waveform + input */}
      <form onSubmit={handleUserSubmit} style={{
        display: 'grid',
        gridTemplateColumns: 'auto 1fr auto',
        gap: 12, alignItems: 'center',
        padding: '14px 16px',
        borderTop: `1px solid ${T.paperLine}`,
        background: T.ink2,
      }}>
        <button
          type="button"
          onMouseDown={startRecording}
          onMouseUp={stopRecording}
          onMouseLeave={() => { if (recording) stopRecording(); }}
          onTouchStart={(e) => { e.preventDefault(); startRecording(); }}
          onTouchEnd={(e) => { e.preventDefault(); stopRecording(); }}
          disabled={finished || awaiting}
          title={lang === 'en' ? 'Hold to talk' : 'Tieni premuto per parlare'}
          style={{
            width: 40, height: 40, borderRadius: 20,
            background: recording ? T.corallo : (finished ? T.ink3 : T.text),
            color: recording ? '#fff' : (finished ? T.muted : T.ink),
            display: 'flex', alignItems: 'center', justifyContent: 'center',
            border: `1px solid ${recording ? T.corallo : (finished ? T.paperLine : T.text)}`,
            cursor: finished || awaiting ? 'default' : 'pointer',
            position: 'relative',
            padding: 0,
          }}>
          <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
            <rect x="9" y="2" width="6" height="12" rx="3"/>
            <path d="M5 10v2a7 7 0 0 0 14 0v-2"/>
            <line x1="12" y1="19" x2="12" y2="22"/>
          </svg>
          {recording && (
            <span style={{
              position: 'absolute', inset: -3, borderRadius: 24,
              border: `2px solid ${T.corallo}`,
              opacity: 0.7, animation: 'lcRing 1.6s ease-out infinite',
            }} />
          )}
        </button>

        <div style={{
          background: T.ink3, border: `1px solid ${T.paperLine}`,
          borderRadius: 10, padding: '8px 14px',
          display: 'flex', alignItems: 'center', gap: 12,
        }}>
          <LiveWaveform active={!finished && !!speaker} speaker={speaker} />
          <input
            value={userDraft}
            onChange={(e) => setUserDraft(e.target.value)}
            disabled={finished || awaiting || recording}
            placeholder={lang === 'en' ? 'Type a message…' : 'Scrivi un messaggio…'}
            style={{
              flex: 1, minWidth: 0, background: 'transparent', border: 'none',
              outline: 'none', fontFamily: T.font, fontSize: 14, color: T.text,
            }}
          />
        </div>

        <button type="submit" disabled={!userDraft.trim() || finished || awaiting} style={{
          padding: '10px 16px', borderRadius: 10,
          background: userDraft.trim() && !finished && !awaiting ? T.corallo : T.ink3,
          color: userDraft.trim() && !finished && !awaiting ? '#fff' : T.muted,
          border: 'none', cursor: userDraft.trim() && !finished && !awaiting ? 'pointer' : 'default',
          fontFamily: T.font, fontWeight: 600, fontSize: 13, letterSpacing: -0.1,
        }}>{lang === 'en' ? 'Send' : 'Invia'}</button>
      </form>

      <style>{`
        @keyframes lcPulse {
          0% { box-shadow: 0 0 0 0 rgba(244,63,94,0.55); }
          70% { box-shadow: 0 0 0 8px rgba(244,63,94,0); }
          100% { box-shadow: 0 0 0 0 rgba(244,63,94,0); }
        }
        @keyframes lcRing {
          0% { transform: scale(1); opacity: 0.7; }
          100% { transform: scale(1.5); opacity: 0; }
        }
      `}</style>
    </div>
  );
}

function Dots({ color }) {
  return (
    <span style={{ display: 'inline-flex', gap: 2 }}>
      {[0,1,2].map(i => (
        <span key={i} style={{
          width: 4, height: 4, borderRadius: 2, background: color,
          animation: `lcDot 1s ease-in-out ${i * 0.18}s infinite`,
          display: 'inline-block',
        }} />
      ))}
      <style>{`@keyframes lcDot { 0%, 60%, 100% { opacity: 0.35; transform: translateY(0); } 30% { opacity: 1; transform: translateY(-2px); } }`}</style>
    </span>
  );
}

function Bubble({ role, text, vertical, lang }) {
  const T = useTheme();
  const isAgent = role === 'agent';
  return (
    <div style={{
      maxWidth: '85%',
      alignSelf: isAgent ? 'flex-start' : 'flex-end',
      animation: 'lcBubble .35s ease-out',
    }}>
      <div style={{
        fontFamily: T.mono, fontSize: 9.5, letterSpacing: 1.8, fontWeight: 700,
        color: isAgent ? T.corallo : T.muted,
        marginBottom: 4,
        textAlign: isAgent ? 'left' : 'right',
      }}>
        {isAgent ? `SCHEDY · ${vertical.name.toUpperCase()}` : (lang === 'en' ? 'CUSTOMER' : 'CLIENTE')}
      </div>
      <div style={{
        padding: '12px 16px',
        borderRadius: isAgent ? '4px 14px 14px 14px' : '14px 4px 14px 14px',
        background: isAgent ? T.ink3 : T.text,
        color: isAgent ? T.text : T.ink,
        border: `1px solid ${isAgent ? T.paperLine : T.text}`,
        fontSize: 14.5, lineHeight: 1.45, fontWeight: isAgent ? 500 : 500,
        wordBreak: 'break-word', overflowWrap: 'anywhere',
      }}>
        {text}
      </div>
      <style>{`
        @keyframes lcBubble {
          from { opacity: 0; transform: translateY(6px); }
          to   { opacity: 1; transform: translateY(0); }
        }
      `}</style>
    </div>
  );
}

// ───────────────────────────────────────────────────────────────────────────
// SIDE PANEL — voice / compliance / captured fields (the "tool" output).

function SidePanel({ active, script, captured, meta, lang }) {
  const T = useTheme();
  const { isMobile } = useViewport();
  if (isMobile && !active) return null;

  const m = meta || {};
  const latencyStr = m.latency != null ? `${m.latency} ms` : '—';
  const voiceStr = m.voice || (lang === 'en' ? 'Text only' : 'Solo testo');
  const modelStr = m.model || (lang === 'en' ? '—' : '—');

  return (
    <div style={{
      display: 'flex', flexDirection: 'column', gap: 12,
    }}>
      <Card label={lang === 'en' ? 'VOICE' : 'VOCE'}>
        <Row k={lang === 'en' ? 'STT' : 'Trascrizione'} v={voiceStr} live={!!m.voice} />
        <Row k={lang === 'en' ? 'Latency' : 'Latenza'} v={latencyStr} live={m.latency != null} />
        <Row k={lang === 'en' ? 'Model' : 'Modello'} v={modelStr} live={!!m.model} />
      </Card>

      <Card label="COMPLIANCE">
        <Row k="GDPR" v="✓" green />
        <Row k="ISO 27001" v="✓" green />
        <Row k={lang === 'en' ? 'Audio storage' : 'Audio salvato'} v={lang === 'en' ? 'opt-in' : 'opt-in'} />
      </Card>

      <Card label={lang === 'en' ? 'CAPTURED' : 'CATTURATI'} accent={active && Object.keys(captured || {}).length > 0}>
        {Object.keys(captured || {}).length > 0
          ? Object.entries(captured).map(([k, v]) => (
              <Row key={k} k={k} v={String(v)} live />
            ))
          : (
            <div style={{ fontSize: 12, color: T.faint, fontStyle: 'italic', padding: '4px 0' }}>
              {lang === 'en'
                ? 'Fields appear here as the agent extracts them from the conversation.'
                : 'I campi compaiono qui mano a mano che l’agente li estrae dalla conversazione.'}
            </div>
          )}
      </Card>
    </div>
  );
}

function Card({ label, children, accent }) {
  const T = useTheme();
  return (
    <div style={{
      background: T.ink2,
      border: `1px solid ${accent ? T.violaBorder : T.paperLine}`,
      borderRadius: 12, padding: '14px 16px',
      boxShadow: accent ? `0 0 0 3px ${T.violaSoft}` : 'none',
      transition: 'box-shadow .2s, border-color .2s',
    }}>
      <div style={{
        fontFamily: T.mono, fontSize: 10, letterSpacing: 2, fontWeight: 700,
        color: accent ? T.corallo : T.faint, marginBottom: 10,
      }}>{label}</div>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>{children}</div>
    </div>
  );
}

function Row({ k, v, green, live }) {
  const T = useTheme();
  return (
    <div style={{
      display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', gap: 12,
      fontSize: 12.5, lineHeight: 1.35,
    }}>
      <span style={{ color: T.muted, fontWeight: 500 }}>{k}</span>
      <span style={{
        color: green ? '#10b981' : (live ? T.text : T.faint),
        fontWeight: live ? 700 : 500,
        fontFamily: live ? T.font : T.font,
        textAlign: 'right', maxWidth: '60%',
        transition: 'color .25s',
      }}>{v}</span>
    </div>
  );
}

// ───────────────────────────────────────────────────────────────────────────
// HERO — composition.

function V7LiveHero({ vertical, onPickVertical, lang, variant }) {
  const T = useTheme();
  const t = useT();
  const { isMobile, isTablet } = useViewport();
  const [callKey, setCallKey] = React.useState(0);
  const [active, setActive] = React.useState(false);
  const [captured, setCaptured] = React.useState({});
  const [meta, setMeta] = React.useState({ latency: null, model: null, voice: null, remaining: null });
  const [sessionId, setSessionId] = React.useState(() => persistedSessionId());
  const [micGranted, setMicGranted] = React.useState(null); // null = unknown, true/false = checked
  const [mode, setMode] = React.useState('chat');           // 'chat' = push-to-talk + text. 'voice' = WebRTC live (Vapi-style).
  // Agent language: which language the bot speaks/replies in. Defaults to the
  // UI language picked in the nav. Visitor can override here without changing
  // the rest of the site. Two voices per language (M / F).
  const [agentLang, setAgentLang] = React.useState(lang);
  const [voice, setVoice] = React.useState('cedar');

  const VOICES_BY_LANG = {
    it: [{ id: 'cedar', label: 'Cedar (uomo)' }, { id: 'marin', label: 'Marin (donna)' }],
    en: [{ id: 'verse', label: 'Verse (male)' }, { id: 'shimmer', label: 'Shimmer (female)' }],
  };
  const currentVoices = VOICES_BY_LANG[agentLang] || VOICES_BY_LANG.it;

  // When agentLang changes, drop the voice back to the male default for that
  // language so we never end up with an EN voice while the bot speaks IT.
  React.useEffect(() => {
    if (!currentVoices.find(v => v.id === voice)) {
      setVoice(currentVoices[0].id);
    }
    // eslint-disable-next-line
  }, [agentLang]);

  // Use cases dropped from the UI (Vapi-style minimalism). SidePanel now
  // discovers the tool dynamically from the agent's first tool_call.
  const script = { tool: 'TOOL', fields: [] };
  const useCaseLabel = vertical.name;

  // Microphone permission probe — Permissions API where available so we can
  // surface "Mic ready" / "Mic permission needed" without forcing the prompt
  // until the visitor actually holds the mic button.
  React.useEffect(() => {
    if (!navigator.permissions || !navigator.permissions.query) return;
    let cancelled = false;
    navigator.permissions.query({ name: 'microphone' }).then(p => {
      if (cancelled) return;
      setMicGranted(p.state === 'granted');
      p.onchange = () => setMicGranted(p.state === 'granted');
    }).catch(() => { /* unsupported, leave as null */ });
    return () => { cancelled = true; };
  }, []);

  function handleVerticalChange(e) {
    const id = e.target.value;
    const list = window.VERTICALS || [];
    const found = list.find(x => x.id === id);
    if (found && onPickVertical) onPickVertical(found);
  }

  function start() {
    setActive(true);
    setCaptured({});
    setMeta({ latency: null, model: null, voice: null, remaining: null });
    setCallKey((k) => k + 1);
  }
  function end() {
    setActive(false);
    setCaptured({});
    // Fresh session for the next call so the agent treats the visitor as new.
    try { sessionStorage.removeItem('hello_demo_session'); } catch {}
    setSessionId(persistedSessionId());
  }
  function restart() {
    setCaptured({});
    setMeta({ latency: null, model: null, voice: null, remaining: null });
    try { sessionStorage.removeItem('hello_demo_session'); } catch {}
    setSessionId(persistedSessionId());
    setCallKey((k) => k + 1);
  }
  function handleCapture(c) { setCaptured((prev) => ({ ...prev, ...c })); }
  function handleMeta(m) { setMeta((prev) => ({ ...prev, ...m })); }

  const verticalsList = (typeof window !== 'undefined' && window.VERTICALS) || [];

  return (
    <section style={{
      padding: isMobile ? '24px 18px 40px' : isTablet ? '36px 36px 52px' : '40px 48px 64px',
      position: 'relative',
    }}>
      {/* CENTERED HERO — Vapi-style: kicker + headline + body + vertical dropdown + Avvia */}
      <div style={{ maxWidth: 760, margin: '0 auto', textAlign: 'center' }}>
        <div style={{ fontFamily: T.mono, fontSize: 11, color: T.corallo, letterSpacing: 2, marginBottom: 18, fontWeight: 700 }}>
          {lang === 'en' ? 'LIVE DEMO · CALL SCHEDY NOW' : 'DEMO LIVE · CHIAMA SCHEDY ORA'}
        </div>
        <h1 style={{
          fontSize: isMobile ? 44 : isTablet ? 60 : 72,
          lineHeight: 0.98, letterSpacing: isMobile ? -1.8 : -3,
          fontWeight: T.hBold, margin: 0, color: T.text,
        }}>
          {lang === 'en' ? 'Talk to Schedy.' : 'Parla con Schedy.'}<br/>
          <span style={{ color: T.corallo, fontStyle: 'italic' }}>
            {lang === 'en' ? 'He answers.' : 'Lui ti risponde.'}
          </span>
        </h1>
        <p style={{
          fontSize: isMobile ? 16 : 18, color: T.muted, lineHeight: 1.55, marginTop: 22,
          maxWidth: 560, marginLeft: 'auto', marginRight: 'auto',
        }}>
          {lang === 'en'
            ? 'Bookings, reschedules, cancellations and info — in real time, by chat or voice. Pick an industry and try it.'
            : 'Prenotazioni, spostamenti, cancellazioni e info — in tempo reale, in chat o a voce. Scegli un settore e provalo.'}
        </p>

        {/* Industry dropdown + Initiate Call button */}
        <div style={{
          marginTop: 32,
          display: 'flex', flexDirection: isMobile ? 'column' : 'row',
          gap: 12, alignItems: 'center', justifyContent: 'center', flexWrap: 'wrap',
        }}>
          <select
            value={vertical.id}
            onChange={handleVerticalChange}
            aria-label={lang === 'en' ? 'Pick an industry' : 'Scegli un settore'}
            style={{
              padding: '14px 18px', borderRadius: 12,
              background: T.ink2, color: T.text,
              border: `1px solid ${T.paperLine}`,
              fontFamily: T.font, fontSize: 15, fontWeight: 600,
              cursor: 'pointer',
              minWidth: isMobile ? 260 : 280,
              maxWidth: '100%',
              appearance: 'auto',
            }}>
            {verticalsList.map(item => (
              <option key={item.id} value={item.id}>{item.name} · {item.place}</option>
            ))}
          </select>

          <button onClick={start} style={{
            padding: '15px 28px', borderRadius: 12,
            background: T.corallo, color: '#fff', border: 'none',
            fontFamily: T.font, fontSize: 15, fontWeight: 700, letterSpacing: -0.1,
            cursor: 'pointer',
            boxShadow: '0 10px 30px rgba(244,63,94,0.32), inset 0 1px 0 rgba(255,255,255,0.15)',
            display: 'inline-flex', alignItems: 'center', gap: 9,
            minWidth: isMobile ? 260 : 'auto',
            justifyContent: 'center',
          }}>
            <svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><path d="M8 5v14l11-7z"/></svg>
            {lang === 'en' ? 'Initiate call' : 'Avvia chiamata'}
          </button>
        </div>

        {/* Mode + voice picker — segmented pill style.
            'chat' = push-to-talk + text (existing /engine/demo-chat).
            'voice' = WebRTC live with OpenAI Realtime API (Vapi-style). */}
        <div style={{
          marginTop: 14,
          display: 'inline-flex', alignItems: 'center', gap: 14, flexWrap: 'wrap',
          justifyContent: 'center',
        }}>
          <div style={{
            display: 'inline-flex', background: T.ink2,
            border: `1px solid ${T.paperLine}`, borderRadius: 999, padding: 3,
          }}>
            {[
              { id: 'chat',  label: lang === 'en' ? 'Chat' : 'Chat' },
              { id: 'voice', label: lang === 'en' ? 'Voice live' : 'Voce live' },
            ].map(opt => (
              <button key={opt.id} onClick={() => setMode(opt.id)} style={{
                padding: '6px 14px', borderRadius: 999,
                background: mode === opt.id ? T.corallo : 'transparent',
                color: mode === opt.id ? '#fff' : T.muted,
                border: 'none', cursor: 'pointer',
                fontFamily: T.font, fontSize: 12, fontWeight: 600, letterSpacing: 0.2,
              }}>{opt.label}</button>
            ))}
          </div>
          {/* Agent language — independent from UI language (nav toggle).
              Default mirrors the UI lang but visitor can override. */}
          <select
            value={agentLang}
            onChange={(e) => setAgentLang(e.target.value)}
            aria-label={lang === 'en' ? 'Agent language' : 'Lingua agente'}
            style={{
              padding: '6px 10px', borderRadius: 8,
              background: T.ink2, color: T.text,
              border: `1px solid ${T.paperLine}`,
              fontFamily: T.font, fontSize: 12, fontWeight: 500,
              cursor: 'pointer',
            }}>
            <option value="it">{lang === 'en' ? 'Italian' : 'Italiano'}</option>
            <option value="en">{lang === 'en' ? 'English' : 'Inglese'}</option>
          </select>

          {mode === 'voice' && (
            <select
              value={voice}
              onChange={(e) => setVoice(e.target.value)}
              aria-label={lang === 'en' ? 'Pick a voice' : 'Scegli una voce'}
              style={{
                padding: '6px 10px', borderRadius: 8,
                background: T.ink2, color: T.text,
                border: `1px solid ${T.paperLine}`,
                fontFamily: T.font, fontSize: 12, fontWeight: 500,
                cursor: 'pointer',
              }}>
              {currentVoices.map(v => (
                <option key={v.id} value={v.id}>{v.label}</option>
              ))}
            </select>
          )}
        </div>

        {/* Mic permission indicator (cosmetic; the real prompt fires on first push-to-talk) */}
        <div style={{
          marginTop: 14, fontSize: 12,
          color: micGranted === false ? T.corallo : T.faint,
          fontFamily: T.mono, letterSpacing: 0.5,
        }}>
          {micGranted === true && (lang === 'en' ? '🎙 Microphone ready' : '🎙 Microfono pronto')}
          {micGranted === false && (lang === 'en' ? '⚠ Mic permission needed for voice' : '⚠ Permesso microfono necessario per la voce')}
          {micGranted === null && (lang === 'en' ? 'Voice + chat available' : 'Voce + chat disponibili')}
        </div>

        <p style={{
          fontSize: 12, color: T.faint, marginTop: 20, fontStyle: 'italic',
          maxWidth: 500, marginLeft: 'auto', marginRight: 'auto', lineHeight: 1.5,
        }}>
          {lang === 'en'
            ? 'Real-time on Schedy — chat and voice processed by the actual agent. Bookings are simulated.'
            : 'In tempo reale su Schedy — chat e voce elaborati dall’agente reale. Prenotazioni simulate.'}
        </p>
      </div>

      {/* Stats strip — same editorial pattern as V6 */}
      <div style={{
        display: 'grid',
        gridTemplateColumns: isMobile ? '1fr 1fr' : 'repeat(4, 1fr)',
        gap: isMobile ? 16 : 28,
        marginTop: isMobile ? 36 : 56,
        paddingTop: 24, borderTop: `1px solid ${T.paperLine}`,
      }}>
        {t.stats.map(([n, l]) => (
          <div key={l}>
            <div style={{ fontSize: 11, color: T.faint, marginBottom: 6, textTransform: 'lowercase', letterSpacing: 0.1, fontStyle: 'italic', fontWeight: 400 }}>{l.toLowerCase()}</div>
            <div style={{ fontSize: isMobile ? 36 : 52, fontWeight: T.hBold, letterSpacing: -2, color: T.text, lineHeight: 0.95, fontVariantNumeric: 'tabular-nums' }}>{n}</div>
          </div>
        ))}
      </div>

      {/* Fullscreen call overlay — Vapi-style. Covers the viewport while the
          call is active. iOS-specific fixes:
            - `100dvh` (dynamic viewport) instead of 100vh: respects Safari's
              collapsible address bar so the bottom edge isn't cut.
            - `env(safe-area-inset-*)` padding: keeps content out of the
              notch + home indicator + landscape rounded corners.
            - `overscrollBehavior: contain`: stops the iOS rubber-band scroll
              from leaking to the underlying landing page. */}
      {active && (
        <div style={{
          position: 'fixed',
          top: 0, left: 0, right: 0, bottom: 0,
          height: '100dvh',
          zIndex: 1000,
          background: T.ink,
          display: 'flex', flexDirection: 'column',
          paddingTop: `max(${isMobile ? 10 : 24}px, env(safe-area-inset-top))`,
          paddingBottom: `max(${isMobile ? 10 : 24}px, env(safe-area-inset-bottom))`,
          paddingLeft: `max(${isMobile ? 10 : 24}px, env(safe-area-inset-left))`,
          paddingRight: `max(${isMobile ? 10 : 24}px, env(safe-area-inset-right))`,
          overflow: 'hidden',
          overscrollBehavior: 'contain',
        }}>
          <div style={{
            flex: '1 1 auto',
            minHeight: 0,
            display: 'grid',
            gridTemplateColumns: isMobile ? '1fr' : '1fr 300px',
            gap: isMobile ? 10 : 20,
            maxWidth: 1100, width: '100%',
            margin: '0 auto',
            alignItems: 'stretch',
          }}>
            <ActiveCall
              key={callKey}
              vertical={vertical}
              script={script}
              useCaseLabel={useCaseLabel}
              lang={agentLang}
              mode={mode}
              voice={voice}
              onEnd={end}
              onReset={restart}
              onCapture={handleCapture}
              onMeta={handleMeta}
              sessionId={sessionId}
            />
            {!isMobile && (
              <SidePanel active={active} script={script} captured={captured} meta={meta} lang={lang} />
            )}
          </div>
        </div>
      )}
    </section>
  );
}

// Wraps SidePanel + listens to ActiveCall's captured state via a global event
// (so we don't have to lift captured state into the Hero). The ActiveCall
// dispatches a CustomEvent 'lc-capture' on each capture; this component
// subscribes and forwards to SidePanel.
function ActiveCapturedWrapper({ script, active, callKey, lang }) {
  const [captured, setCaptured] = React.useState({});
  React.useEffect(() => {
    setCaptured({});
  }, [callKey, script]);

  React.useEffect(() => {
    function onCap(e) {
      setCaptured((c) => ({ ...c, ...e.detail }));
    }
    window.addEventListener('lc-capture', onCap);
    return () => window.removeEventListener('lc-capture', onCap);
  }, []);

  return <SidePanel active={active} script={script} captured={captured} lang={lang} />;
}

// Patch ActiveCall to also dispatch the lc-capture event when captures land.
// We do this by monkey-patching the setCaptured pathway via window event.
// Instead of patching, we'll let ActiveCall dispatch the event itself.
// (Done below in a small modification: see useEffect block where captures
// land — but easier: we add a second useEffect inside ActiveCall.)

Object.assign(window, {
  USE_CASES, buildScripts,
  LiveWaveform, UseCasePicker, IdlePreview, ActiveCall, SidePanel, V7LiveHero,
});
