// ─── NexWall Corp AI — Shared UI Components v2 ───

// Phase 1/2 palette: violet-dominant dark dashboard (aurora-inspired).
// Backup of the previous blue-dominant palette: git tag `backup-pre-phase1-dashboard`.
// - accent: violet primary (#8b5cf6) — everywhere that used to read as blue now reads as violet
// - accent2: lighter lavender (#a78bfa) used as gradient complement
// - bg layers: very dark with subtle violet tint so glass-morphism reads as tinted, not neutral
// - borders: violet-tinted alpha so cards feel part of the nebula, not gray plates
const NX = {
  bg: '#0a0610', bg2: '#120a1e', bg3: '#1a1128', bg4: '#241838',
  border: 'rgba(139,92,246,0.10)', border2: 'rgba(139,92,246,0.22)',
  text: '#f0eeec', muted: '#8a85a0', muted2: '#2a2135',
  accent: '#8b5cf6', accent2: '#a78bfa',
  accentGrad: 'linear-gradient(135deg, #7c3aed 0%, #8b5cf6 50%, #a78bfa 100%)',
  accentGradCool: 'linear-gradient(135deg, #8b5cf6 0%, #a78bfa 100%)',
  success: '#34d399', warning: '#fbbf24', danger: '#f87171',
  glow: '0 0 40px rgba(139,92,246,0.35)',
};

// ─── BUTTON ───
const NxButton = ({ children, variant='primary', size='md', onClick, disabled, full, icon, type='button' }) => {
  const [hover, setHover] = React.useState(false);
  const base = {
    display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
    gap: '8px', borderWidth: 0, borderStyle: 'solid', borderColor: 'transparent', cursor: disabled ? 'not-allowed' : 'pointer',
    fontFamily: "'DM Sans', sans-serif", fontWeight: 500,
    borderRadius: size === 'sm' ? '10px' : '12px',
    transition: 'all 0.2s cubic-bezier(.4,0,.2,1)', width: full ? '100%' : 'auto',
    opacity: disabled ? 0.4 : 1,
    ...(size === 'sm' ? { fontSize: '13px', padding: '7px 14px' } :
        size === 'lg' ? { fontSize: '15px', padding: '14px 30px' } :
                        { fontSize: '14px', padding: '11px 22px' }),
    ...(variant === 'primary' ? { background: NX.text, color: NX.bg } :
        variant === 'accent' ? { background: NX.accentGrad, color: '#fff', boxShadow: hover && !disabled ? '0 8px 28px rgba(139,92,246,0.45)' : '0 4px 14px rgba(139,92,246,0.25)' } :
        variant === 'ghost' ? { background: 'transparent', color: NX.muted, borderWidth: '1px', borderStyle: 'solid', borderColor: NX.border } :
        variant === 'danger' ? { background: 'rgba(248,113,113,0.1)', color: NX.danger, borderWidth: '1px', borderStyle: 'solid', borderColor: 'rgba(248,113,113,0.2)' } :
        variant === 'success' ? { background: 'rgba(52,211,153,0.1)', color: NX.success, borderWidth: '1px', borderStyle: 'solid', borderColor: 'rgba(52,211,153,0.2)' } : {}),
    ...(hover && !disabled ? {
      ...(variant==='primary' ? {opacity:0.88} : variant==='ghost' ? {color:NX.text, borderColor:NX.border2, background:'rgba(139,92,246,0.06)'} : variant==='accent' ? {} : {opacity:0.85}),
      transform: 'translateY(-1px)'
    } : {}),
  };
  return (
    <button type={type} style={base} onClick={!disabled ? onClick : undefined}
      onMouseEnter={()=>setHover(true)} onMouseLeave={()=>setHover(false)}>
      {icon}{children}
    </button>
  );
};

// ─── CARD ───
// Glass-morphism: semi-transparent tinted bg + backdrop blur so cards feel layered over the
// aurora background. Backup of previous solid-card look is in git tag backup-pre-phase1-dashboard.
const NxCard = ({ children, style, onClick, hover: hoverProp, active, padding='24px' }) => {
  const [hovered, setHovered] = React.useState(false);
  return (
    <div style={{
      background: active ? 'rgba(36,24,56,0.72)' : 'rgba(26,17,40,0.62)',
      border: `1px solid ${active ? NX.border2 : NX.border}`,
      borderRadius: '16px', padding,
      cursor: onClick ? 'pointer' : 'default',
      transition: 'all 0.25s cubic-bezier(.4,0,.2,1)',
      backdropFilter: 'blur(16px)',
      WebkitBackdropFilter: 'blur(16px)',
      boxShadow: active
        ? '0 8px 32px rgba(139,92,246,0.12), inset 0 1px 0 rgba(255,255,255,0.04)'
        : 'inset 0 1px 0 rgba(255,255,255,0.03)',
      ...(hoverProp && hovered ? {
        background: 'rgba(36,24,56,0.78)',
        borderColor: NX.border2,
        transform: 'translateY(-2px)',
        boxShadow: '0 12px 40px rgba(139,92,246,0.18), inset 0 1px 0 rgba(255,255,255,0.05)',
      } : {}),
      ...style,
    }} onClick={onClick} onMouseEnter={()=>setHovered(true)} onMouseLeave={()=>setHovered(false)}>
      {children}
    </div>
  );
};

// ─── BADGE ───
const NxBadge = ({ children, color='accent' }) => {
  const c = {
    accent: { bg:'rgba(139,92,246,0.12)', text:NX.accent, border:'rgba(139,92,246,0.2)' },
    success: { bg:'rgba(52,211,153,0.1)', text:NX.success, border:'rgba(52,211,153,0.2)' },
    warning: { bg:'rgba(251,191,36,0.1)', text:NX.warning, border:'rgba(251,191,36,0.2)' },
    muted: { bg:NX.bg4, text:NX.muted, border:NX.border },
    purple: { bg:'rgba(139,92,246,0.1)', text:NX.accent2, border:'rgba(139,92,246,0.2)' },
  }[color] || { bg:'rgba(139,92,246,0.12)', text:NX.accent, border:'rgba(139,92,246,0.2)' };
  return (
    <span style={{
      display:'inline-flex', alignItems:'center', gap:'5px',
      background:c.bg, color:c.text, border:`1px solid ${c.border}`,
      borderRadius:'100px', fontSize:'11px', fontWeight:500,
      padding:'3px 10px', fontFamily:"'DM Mono',monospace", letterSpacing:'0.04em',
    }}>{children}</span>
  );
};

// ─── INPUT ───
const NxInput = ({ placeholder, value, onChange, type='text', icon, full }) => {
  const [focus, setFocus] = React.useState(false);
  return (
    <div style={{ position:'relative', width: full ? '100%' : 'auto' }}>
      {icon && <span style={{ position:'absolute', left:'12px', top:'50%', transform:'translateY(-50%)', color:NX.muted, display:'flex' }}>{icon}</span>}
      <input type={type} placeholder={placeholder} value={value} onChange={onChange}
        onFocus={()=>setFocus(true)} onBlur={()=>setFocus(false)}
        style={{
          background:NX.bg3, border:`1px solid ${focus ? NX.border2 : NX.border}`,
          borderRadius:'10px', color:NX.text, fontSize:'14px', padding:'10px 14px',
          paddingLeft: icon ? '36px' : '14px', fontFamily:"'DM Sans',sans-serif",
          outline:'none', width: full ? '100%' : 'auto', boxSizing:'border-box',
          transition:'border-color 0.15s',
        }}/>
    </div>
  );
};

// ─── PROGRESS STEPS ───
// Labels hidden via CSS below ~820px — the step title in the flow already tells the user
// where they are, so the tiny per-circle label just caused overlap on phones.
const NxProgress = ({ steps, current, completed }) => (
  <div className="nx-progress" style={{ display:'flex', alignItems:'center', gap:0, marginBottom:'32px' }}>
    {steps.map((s,i) => (
      <React.Fragment key={i}>
        <div className="nx-progress-step" style={{ display:'flex', alignItems:'center', flexDirection:'column', gap:'6px', minWidth: 0 }}>
          <div className="nx-progress-circle" style={{
            width:'32px', height:'32px', borderRadius:'50%', display:'flex', alignItems:'center', justifyContent:'center',
            background: completed.includes(i) ? NX.accent : i===current ? 'transparent' : NX.bg3,
            border:`2px solid ${completed.includes(i) ? NX.accent : i===current ? NX.accent : NX.border}`,
            fontSize:'12px', fontWeight:600, flexShrink: 0,
            color: completed.includes(i) ? '#fff' : i===current ? NX.accent : NX.muted,
            transition:'all 0.3s',
          }}>
            {completed.includes(i)
              ? <svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2.5 7l3 3.5 5.5-7" stroke="white" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/></svg>
              : i+1}
          </div>
          <span className="nx-progress-label" style={{ fontSize:'10px', color: i===current ? NX.accent : NX.muted, fontFamily:"'DM Mono',monospace", whiteSpace:'nowrap' }}>{s}</span>
        </div>
        {i < steps.length-1 && <div className="nx-progress-connector" style={{ flex:1, height:'2px', background: completed.includes(i) ? NX.accent : NX.border, transition:'background 0.3s', marginBottom:'20px', minWidth: '8px' }}/>}
      </React.Fragment>
    ))}
  </div>
);

// ─── MODAL ───
// `maxWidth` overrides the default size (480 for compact, 1080 for `wide`). Use it when a
// specific step has less content and you want the frame to hug it tighter — e.g. the
// preview/launch screen which has fewer panels than the rest of the flow.
const NxModal = ({ children, onClose, wide, noBackdropClose, maxWidth }) => (
  <div style={{
    position:'fixed', inset:0, background:'rgba(0,0,0,0.72)', backdropFilter:'blur(16px)',
    zIndex:50, display:'flex', alignItems:'center', justifyContent:'center', padding:'20px',
  }} onClick={e => e.target===e.currentTarget && !noBackdropClose && onClose && onClose()}>
    <div className={wide ? 'nx-modal-wide' : ''} style={{
      background:NX.bg2, border:`1px solid ${NX.border2}`, borderRadius:'22px',
      width:'100%', maxWidth: maxWidth || (wide ? '1080px' : '480px'), maxHeight:'92vh', overflow:'auto',
      animation:'modalIn 0.2s ease',
      boxShadow: '0 30px 80px rgba(10,6,16,0.8), 0 0 0 1px rgba(139,92,246,0.06)',
      transition: 'max-width 0.25s ease',
    }}>{children}</div>
  </div>
);

// ─── DIVIDER ───
const NxDivider = ({ label }) => (
  <div style={{ display:'flex', alignItems:'center', gap:'12px', margin:'20px 0' }}>
    <div style={{ flex:1, height:'1px', background:NX.border }}/>
    {label && <span style={{ fontSize:'11px', color:NX.muted, fontFamily:"'DM Mono',monospace" }}>{label}</span>}
    <div style={{ flex:1, height:'1px', background:NX.border }}/>
  </div>
);

// ─── ROBOT AI ICON ───
const RobotIcon = ({ size=32, animated=false }) => {
  const s = size;
  return (
    <svg width={s} height={s} viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
      <defs>
        <linearGradient id="rg1" x1="0" y1="0" x2="48" y2="48" gradientUnits="userSpaceOnUse">
          <stop stopColor="#8b5cf6"/><stop offset="1" stopColor="#8b5cf6"/>
        </linearGradient>
        <filter id="rglow"><feGaussianBlur stdDeviation="1.5" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge></filter>
      </defs>
      {/* Antenna */}
      <line x1="24" y1="11" x2="24" y2="5" stroke="url(#rg1)" strokeWidth="1.5" strokeLinecap="round"/>
      <circle cx="24" cy="4" r="2.5" fill="#8b5cf6" filter="url(#rglow)"
        style={animated ? { animation:'antennaPulse 1.8s ease-in-out infinite' } : {}}/>
      {/* Head */}
      <rect x="8" y="11" width="32" height="26" rx="6" stroke="url(#rg1)" strokeWidth="1.5" fill="rgba(139,92,246,0.05)"/>
      {/* Circuit lines on head */}
      <path d="M8 22h4M36 22h4" stroke="url(#rg1)" strokeWidth="1" opacity="0.4" strokeLinecap="round"/>
      {/* Eyes */}
      <rect x="14" y="18" width="7" height="7" rx="2" fill="url(#rg1)" opacity="0.9"
        style={animated ? { animation:'eyeGlow 2s ease-in-out infinite' } : {}}/>
      <rect x="27" y="18" width="7" height="7" rx="2" fill="url(#rg1)" opacity="0.9"
        style={animated ? { animation:'eyeGlow 2s ease-in-out infinite' } : {}}/>
      {/* Eye shine */}
      <rect x="15.5" y="19.5" width="2" height="2" rx="0.5" fill="white" opacity="0.6"/>
      <rect x="28.5" y="19.5" width="2" height="2" rx="0.5" fill="white" opacity="0.6"/>
      {/* Mouth grid */}
      <rect x="15" y="30" width="18" height="4" rx="2" fill="none" stroke="url(#rg1)" strokeWidth="1" opacity="0.6"/>
      <line x1="19" y1="30" x2="19" y2="34" stroke="url(#rg1)" strokeWidth="0.8" opacity="0.4"/>
      <line x1="24" y1="30" x2="24" y2="34" stroke="url(#rg1)" strokeWidth="0.8" opacity="0.4"/>
      <line x1="29" y1="30" x2="29" y2="34" stroke="url(#rg1)" strokeWidth="0.8" opacity="0.4"/>
      {/* Ears */}
      <rect x="4" y="19" width="4" height="8" rx="2" fill="none" stroke="url(#rg1)" strokeWidth="1.2" opacity="0.5"/>
      <rect x="40" y="19" width="4" height="8" rx="2" fill="none" stroke="url(#rg1)" strokeWidth="1.2" opacity="0.5"/>
    </svg>
  );
};

// ─── BINARY CLOUD ROBOT (logo version with floating bits) ───
const RobotLogoIcon = ({ size=32 }) => (
  <div style={{ position:'relative', width:size, height:size, display:'inline-flex', alignItems:'center', justifyContent:'center' }}>
    <RobotIcon size={size}/>
  </div>
);

// ─── MASCOT (the NexWall AI character — replaces /icono.png in AI-persona contexts) ───
// Estática y nítida. Sin eye-tracking, sin float idle, sin orbits.
const Mascot = ({ size=200, glow=true }) => {
  const containerStyle = {
    position:'relative', width: size, height: size, flexShrink: 0,
  };
  const mediaStyle = {
    width:'100%', height:'100%', objectFit:'contain', display:'block',
    filter: glow ? 'drop-shadow(0 0 28px rgba(91,142,240,0.45)) drop-shadow(0 0 70px rgba(139,92,246,0.28))' : 'none',
  };
  return (
    <div style={containerStyle}>
      <img src="/mascota.png" alt="" style={mediaStyle}/>
    </div>
  );
};

// ─── RIGHT FLOATING ISLAND NAV ───
const RightNav = ({ view, setView, stepsCompleted, lang='es', isAdmin=false }) => {
  const [tooltip, setTooltip] = React.useState(null);
  const tn = (I18N && I18N[lang]) ? I18N[lang].nav : {};

  const items = [
    { id:'onboarding', label: tn.onboarding || 'Primeros pasos',
      icon:<svg width="18" height="18" viewBox="0 0 20 20" fill="none"><path d="M3 10L10 3l7 7" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/><path d="M5 8v8h4v-4h2v4h4V8" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/></svg> },
    { id:'estrategias', label: tn.estrategias || 'Mis estrategias',
      icon:<svg width="18" height="18" viewBox="0 0 20 20" fill="none"><path d="M2 16l4-9 3 5 3-4 4 8" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/></svg> },
    { id:'integraciones', label: tn.integraciones || 'Integraciones',
      icon:<svg width="18" height="18" viewBox="0 0 20 20" fill="none"><path d="M13 4a3 3 0 110 6 3 3 0 010-6zM7 10a3 3 0 110 6 3 3 0 010-6z" stroke="currentColor" strokeWidth="1.3"/><line x1="10" y1="7" x2="10" y2="13" stroke="currentColor" strokeWidth="1.3" strokeLinecap="round"/></svg> },
    { id:'marca', label: tn.marca || 'Marca',
      icon:<svg width="18" height="18" viewBox="0 0 20 20" fill="none"><polygon points="10,2 12.5,7.5 18,7.5 14,11 15.5,17 10,13.5 4.5,17 6,11 2,7.5 7.5,7.5" stroke="currentColor" strokeWidth="1.3" strokeLinejoin="round"/></svg> },
    { id:'precios', label: tn.precios || 'Planes y precios',
      icon:<svg width="18" height="18" viewBox="0 0 20 20" fill="none"><rect x="3" y="6" width="14" height="10" rx="2" stroke="currentColor" strokeWidth="1.3"/><path d="M7 6V5a3 3 0 016 0v1" stroke="currentColor" strokeWidth="1.3"/></svg> },
    { id:'historial', label: tn.historial || (lang === 'en' ? 'History' : 'Historial'),
      icon:<svg width="18" height="18" viewBox="0 0 20 20" fill="none"><circle cx="10" cy="10" r="7.5" stroke="currentColor" strokeWidth="1.3"/><path d="M10 6v4l2.5 2" stroke="currentColor" strokeWidth="1.3" strokeLinecap="round"/><path d="M2.5 7L4 5.5M17.5 7L16 5.5" stroke="currentColor" strokeWidth="1.3" strokeLinecap="round"/></svg> },
    ...(isAdmin ? [{
      id: 'admin',
      label: lang === 'en' ? 'Admin dashboard' : 'Panel admin',
      icon: <svg width="18" height="18" viewBox="0 0 20 20" fill="none"><path d="M10 2l7 4v4c0 4-3 7-7 8-4-1-7-4-7-8V6l7-4z" stroke="currentColor" strokeWidth="1.3" strokeLinejoin="round"/><path d="M7 10l2 2 4-4" stroke="currentColor" strokeWidth="1.3" strokeLinecap="round" strokeLinejoin="round"/></svg>,
    }] : []),
  ];

  const completedCount = stepsCompleted.length;

  return (
    <div style={{
      // Phase 1: island moved to LEFT side per user request. Backup: backup-pre-phase1-dashboard.
      position:'fixed', left:'20px', top:'50%', transform:'translateY(-50%)',
      zIndex:40, display:'flex', flexDirection:'column', gap:'4px',
      background:'rgba(18,10,30,0.78)', backdropFilter:'blur(28px)',
      WebkitBackdropFilter:'blur(28px)',
      border:`1px solid ${NX.border2}`,
      borderRadius:'28px', padding:'10px 8px',
      boxShadow:'0 8px 40px rgba(139,92,246,0.18), inset 0 1px 0 rgba(255,255,255,0.05)',
    }}>
      {items.map(item => {
        const active = view === item.id;
        return (
          <div key={item.id} style={{ position:'relative' }}
            onMouseEnter={() => setTooltip(item.id)}
            onMouseLeave={() => setTooltip(null)}>
            <button
              onClick={() => setView(item.id)}
              style={{
                width:'42px', height:'42px', borderRadius:'20px', border:'none',
                cursor:'pointer', display:'flex', alignItems:'center', justifyContent:'center',
                transition:'all 0.2s',
                background: active
                  ? 'linear-gradient(135deg, rgba(139,92,246,0.28), rgba(167,139,250,0.22))'
                  : 'transparent',
                color: active ? NX.accent : NX.muted,
                boxShadow: active ? '0 0 20px rgba(139,92,246,0.35)' : 'none',
                transform: active ? 'scale(1.08)' : 'scale(1)',
              }}>
              {item.id === 'onboarding' && completedCount < 4 ? (
                <div style={{ position:'relative' }}>
                  {item.icon}
                  <div style={{ position:'absolute', top:'-4px', right:'-4px', width:'8px', height:'8px', borderRadius:'50%', background:NX.accent, fontSize:'0' }}/>
                </div>
              ) : item.icon}
            </button>
            {/* Tooltip (island is now on the LEFT, so tooltip appears to the right of the pills) */}
            {tooltip === item.id && (
              <div style={{
                position:'absolute', left:'52px', top:'50%', transform:'translateY(-50%)',
                background:'rgba(18,10,30,0.95)', border:`1px solid ${NX.border2}`,
                borderRadius:'10px', padding:'7px 12px', whiteSpace:'nowrap',
                fontSize:'12px', color:NX.text, fontWeight:500, pointerEvents:'none',
                backdropFilter:'blur(12px)',
                boxShadow:'0 4px 16px rgba(139,92,246,0.25)',
              }}>
                {item.label}
                {item.id==='onboarding' && <span style={{ color:NX.accent, marginLeft:'8px', fontFamily:"'DM Mono',monospace", fontSize:'10px' }}>{completedCount}/4</span>}
                {/* arrow pointing to the island (to the left) */}
                <div style={{ position:'absolute', left:'-5px', top:'50%', transform:'translateY(-50%)', width:0, height:0, borderTop:'5px solid transparent', borderBottom:'5px solid transparent', borderRight:`5px solid ${NX.border2}` }}/>
              </div>
            )}
          </div>
        );
      })}
    </div>
  );
};

// ─── CREDITS PILL ───
// Saldo de créditos visible en TopBar. Hace polling cada 60s y se actualiza inmediatamente
// cuando window dispara 'nx-credits-changed' (después de cualquier acción IA exitosa).
// Se oculta para admins/usuarios sin plan (los gates ya muestran el modal apropiado).
const CreditsPill = ({ lang='es', onClick }) => {
  const [data, setData] = React.useState(null);
  const [hover, setHover] = React.useState(false);
  // Usa cache compartido (window.getCreditsBalance) — los 3 consumidores del balance
  // (pill, asistente, business-limit) comparten una sola llamada al backend cada 30s
  // en lugar de 3 paralelas en cada page-load.
  const fetchBalance = React.useCallback(async () => {
    if (!window.getCreditsBalance) return;
    const j = await window.getCreditsBalance(typeof API !== 'undefined' ? API : '');
    if (j) setData(j);
  }, []);
  React.useEffect(() => {
    fetchBalance();
    const onChange = () => fetchBalance();
    window.addEventListener('nx-credits-changed', onChange);
    const t = setInterval(fetchBalance, 60000);
    return () => { window.removeEventListener('nx-credits-changed', onChange); clearInterval(t); };
  }, [fetchBalance]);

  if (!data) return null;

  const balance = Number(data.balance || 0);
  const planTotal = Number(data.plan_credits || 0);
  // Admin y reviewer SIEMPRE muestran ∞ — sus llamadas IA no descuentan créditos en backend.
  const unlimited = !!data.is_admin || data.plan === 'reviewer';
  const noPlan = !data.plan && !data.is_admin && balance <= 0;
  const ratio = planTotal > 0 ? Math.max(0, Math.min(1, balance / planTotal)) : 0;
  const low = !unlimited && !noPlan && ratio < 0.15;
  const empty = !unlimited && (noPlan || balance <= 0);
  const color = unlimited ? NX.success : empty ? NX.danger : low ? NX.warning : NX.accent2;
  const bg = unlimited ? 'rgba(52,211,153,0.10)' : empty ? 'rgba(248,113,113,0.10)' : low ? 'rgba(251,191,36,0.10)' : 'rgba(139,92,246,0.10)';
  const border = unlimited ? 'rgba(52,211,153,0.28)' : empty ? 'rgba(248,113,113,0.28)' : low ? 'rgba(251,191,36,0.28)' : 'rgba(139,92,246,0.22)';

  const formatBalance = (n) => {
    if (unlimited) return '∞';
    if (noPlan) return lang === 'en' ? 'No plan' : 'Sin plan';
    if (n >= 1000) return (n / 1000).toFixed(n >= 10000 ? 0 : 1).replace('.0', '') + 'k';
    return String(n);
  };
  const periodLabel = data.period_end
    ? new Date(data.period_end).toLocaleDateString(lang === 'en' ? 'en-US' : 'es-CL', { day: 'numeric', month: 'short' })
    : '';
  const tooltip = unlimited
    ? (lang === 'en' ? 'Unlimited credits (admin / reviewer)' : 'Créditos ilimitados (admin / reviewer)')
    : noPlan
      ? (lang === 'en' ? 'No active plan — click to view plans' : 'Sin plan activo — click para ver planes')
      : (lang === 'en'
          ? `${balance.toLocaleString('en-US')} of ${planTotal.toLocaleString('en-US')} credits — resets ${periodLabel}`
          : `${balance.toLocaleString('es-CL')} de ${planTotal.toLocaleString('es-CL')} créditos — reset ${periodLabel}`);

  return (
    <button
      onClick={onClick}
      onMouseEnter={() => setHover(true)}
      onMouseLeave={() => setHover(false)}
      title={tooltip}
      style={{
        display: 'inline-flex', alignItems: 'center', gap: '7px',
        padding: '7px 14px', borderRadius: '100px',
        background: hover ? bg.replace('0.10', '0.20') : bg,
        border: `1px solid ${border}`,
        color, cursor: 'pointer', flexShrink: 0,
        fontFamily: "'DM Mono', monospace", fontSize: '14px', fontWeight: 700,
        letterSpacing: '0.02em', transition: 'all 0.15s',
        boxShadow: hover ? `0 4px 14px ${border}` : 'none',
      }}
    >
      <svg width="15" height="15" viewBox="0 0 16 16" fill="none">
        <circle cx="8" cy="8" r="6" stroke="currentColor" strokeWidth="1.5"/>
        <path d="M8 4.5v3.7l2.2 1.3" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round"/>
      </svg>
      <span style={{ display: 'inline-flex', alignItems: 'baseline', gap: '3px' }}>
        {formatBalance(balance)}
        {!unlimited && !noPlan && (
          <span style={{ fontSize: '10px', fontWeight: 500, opacity: 0.7 }}>cr</span>
        )}
      </span>
    </button>
  );
};

// ─── NO CREDITS MODAL ───
// Escucha el evento global 'nx-credits-blocked' (disparado por window.nxFetch en credits.js
// cuando el backend devuelve 402) y muestra un modal con CTA "Ver planes". Montado una sola
// vez al nivel de App, cubre TODOS los flujos sin que cada uno tenga que manejar 402.
const NoCreditsModal = ({ lang='es', onGoToPricing }) => {
  const [info, setInfo] = React.useState(null);
  React.useEffect(() => {
    const onBlock = (e) => setInfo(e.detail || {});
    window.addEventListener('nx-credits-blocked', onBlock);
    return () => window.removeEventListener('nx-credits-blocked', onBlock);
  }, []);
  if (!info) return null;
  const isPlanRequired = info.code === 'PLAN_REQUIRED';
  const required = Number(info.required || 0);
  const balance = Number(info.balance || 0);
  const missing = Math.max(0, required - balance);

  const t = lang === 'en' ? {
    titlePlan: 'Subscription required',
    titleCredits: 'Out of credits',
    bodyPlan: 'Generating with AI requires an active Business plan.',
    bodyCredits: `You need ${required.toLocaleString('en-US')} credits but only have ${balance.toLocaleString('en-US')}. Missing ${missing.toLocaleString('en-US')}.`,
    cta: 'View plans',
    close: 'Close',
  } : {
    titlePlan: 'Suscripción requerida',
    titleCredits: 'Sin créditos',
    bodyPlan: 'Generar con IA requiere un plan Business activo.',
    bodyCredits: `Necesitas ${required.toLocaleString('es-CL')} créditos pero solo tienes ${balance.toLocaleString('es-CL')}. Faltan ${missing.toLocaleString('es-CL')}.`,
    cta: 'Ver planes',
    close: 'Cerrar',
  };

  return (
    <div onClick={() => setInfo(null)} style={{
      position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.65)', backdropFilter: 'blur(6px)',
      display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 9999,
      animation: 'modalIn 0.18s ease',
    }}>
      <div onClick={e => e.stopPropagation()} style={{
        background: NX.bg2, border: `1px solid ${NX.border2}`, borderRadius: '16px',
        padding: '28px', maxWidth: '420px', width: '92%', textAlign: 'center',
        boxShadow: '0 20px 60px rgba(0,0,0,0.6)',
      }}>
        <div style={{ width: '56px', height: '56px', borderRadius: '50%', background: 'rgba(248,113,113,0.12)', border: '1px solid rgba(248,113,113,0.28)', margin: '0 auto 16px', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
          <svg width="24" height="24" viewBox="0 0 24 24" fill="none">
            <circle cx="12" cy="12" r="9" stroke={NX.danger} strokeWidth="1.6"/>
            <path d="M12 7v6M12 16v.5" stroke={NX.danger} strokeWidth="1.6" strokeLinecap="round"/>
          </svg>
        </div>
        <h3 style={{ fontSize: '18px', fontWeight: 600, color: NX.text, margin: '0 0 8px' }}>
          {isPlanRequired ? t.titlePlan : t.titleCredits}
        </h3>
        <p style={{ fontSize: '14px', color: NX.muted, lineHeight: 1.5, margin: '0 0 24px' }}>
          {isPlanRequired ? t.bodyPlan : t.bodyCredits}
        </p>
        <div style={{ display: 'flex', gap: '8px', justifyContent: 'center' }}>
          <NxButton variant="ghost" onClick={() => setInfo(null)}>{t.close}</NxButton>
          <NxButton variant="accent" onClick={() => { setInfo(null); if (onGoToPricing) onGoToPricing(); }}>{t.cta}</NxButton>
        </div>
      </div>
    </div>
  );
};

// ─── BUSINESS SWITCHER + TOP BAR ───
// Per-user cache key so two accounts in the same browser never share a businesses cache.
// Cross-account contamination of `nw_biz` previously caused mutations of one account's DB
// rows when the other account's stale cache was migrated up via /api/businesses/migrate.
const bizCacheKey = () => {
  const uid = localStorage.getItem('nw_current_uid');
  return uid ? `nw_biz_${uid}` : 'nw_biz';
};
const TopBar = ({ onMobileMenu, currentUser, onLogout, lang='es', onToggleLang, onBizChange, onCreditsClick }) => {
  const tt = (I18N && I18N[lang]) ? I18N[lang].topbar : {};
  // Businesses live in the backend (table `businesses`). Keep a localStorage cache purely for
  // instant paint — on mount we always reconcile against GET /api/businesses.
  const [businesses, setBusinesses] = React.useState(() => {
    try {
      const cached = JSON.parse(localStorage.getItem(bizCacheKey()));
      if (Array.isArray(cached) && cached.length) return cached;
    } catch {}
    return [{ id: '1', name: 'Tienda Principal', emoji: '🏪' }];
  });
  const [currentBiz, setCurrentBiz] = React.useState(() => {
    try {
      const list = JSON.parse(localStorage.getItem(bizCacheKey())) || [{ id: '1' }];
      const activeId = localStorage.getItem('nw_active_biz_id');
      const idx = activeId ? list.findIndex(b => String(b.id) === activeId) : 0;
      return idx >= 0 ? idx : 0;
    } catch { return 0; }
  });
  const [open, setOpen] = React.useState(false);
  const [adding, setAdding] = React.useState(false);
  const [newName, setNewName] = React.useState('');
  const [renamingIdx, setRenamingIdx] = React.useState(-1);
  const [renameValue, setRenameValue] = React.useState('');

  const EMOJIS = ['🍽️','☕','🛍️','💈','🏋️','📱','🎨','🏪','🚗','💊'];

  // Write-through cache: every time `businesses` changes we persist to localStorage so the next
  // mount paints instantly before the backend responds.
  const persistBusinesses = (list) => {
    setBusinesses(list);
    try { localStorage.setItem(bizCacheKey(), JSON.stringify(list)); } catch {}
  };

  // Load businesses from backend on mount. The legacy "migrate stale localStorage to DB"
  // path was removed — it was the root cause of cross-account business-name mutations
  // when two sessions shared the same browser. The DB is now the single source of truth.
  React.useEffect(() => {
    let cancelled = false;
    (async () => {
      try {
        const res = await authFetch('/api/businesses');
        if (!res.ok) return;
        const data = await res.json();
        if (cancelled || !Array.isArray(data) || data.length === 0) return;
        // Normalize id to string for consistent comparisons across the app.
        const normalized = data.map(b => ({ id: String(b.id), name: b.name, emoji: b.emoji || '🏪' }));
        persistBusinesses(normalized);
        // Reconcile activeBizId with what's in the backend.
        const activeId = localStorage.getItem('nw_active_biz_id');
        const idx = activeId ? normalized.findIndex(b => String(b.id) === activeId) : 0;
        const safeIdx = idx >= 0 ? idx : 0;
        setCurrentBiz(safeIdx);
        const b = normalized[safeIdx];
        if (b) {
          localStorage.setItem('nw_active_biz_id', String(b.id));
          // Notify parent only if the active biz actually changed to prevent a reload loop.
          if (onBizChange && String(b.id) !== activeId) onBizChange(b.id);
        }
      } catch (e) {
        console.warn('[businesses] load failed:', e.message);
      }
    })();
    return () => { cancelled = true; };
  }, []);

  const setBiz = (idx) => {
    setCurrentBiz(idx);
    const b = businesses[idx] || businesses[0];
    if (b) {
      localStorage.setItem('nw_active_biz_id', String(b.id));
      if (onBizChange) onBizChange(b.id);
    }
  };

  // Persist active biz on mount
  React.useEffect(() => {
    const b = businesses[currentBiz] || businesses[0];
    if (b) localStorage.setItem('nw_active_biz_id', String(b.id));
  }, []);

  const addBusiness = async () => {
    if (!newName.trim()) return;
    const emoji = EMOJIS[businesses.length % EMOJIS.length];
    try {
      const res = await authFetch('/api/businesses', {
        method: 'POST',
        body: JSON.stringify({ name: newName.trim(), emoji }),
      });
      const data = await res.json().catch(() => ({}));
      if (!res.ok) {
        if (data.code === 'BUSINESS_LIMIT_REACHED') {
          alert(data.error || (lang === 'en' ? 'Plan limit reached' : 'Límite del plan alcanzado'));
        } else {
          alert(data.error || 'Error');
        }
        return;
      }
      const normalized = { id: String(data.id), name: data.name, emoji: data.emoji };
      const next = [...businesses, normalized];
      persistBusinesses(next);
      const newIdx = next.length - 1;
      setCurrentBiz(newIdx);
      localStorage.setItem('nw_active_biz_id', String(normalized.id));
      if (onBizChange) onBizChange(normalized.id);
    } catch (e) {
      console.warn('[businesses] create failed:', e.message);
    }
    setNewName('');
    setAdding(false);
    setOpen(false);
  };

  // Lookup del límite del plan para mostrar contador "X / Y" y deshabilitar el botón "+ Nuevo".
  // Reutiliza cache compartido para evitar duplicar la llamada con CreditsPill.
  const [planInfo, setPlanInfo] = React.useState(null);
  React.useEffect(() => {
    let cancelled = false;
    (async () => {
      if (!window.getCreditsBalance) return;
      const j = await window.getCreditsBalance(typeof API !== 'undefined' ? API : '');
      if (!cancelled && j) setPlanInfo(j);
    })();
    return () => { cancelled = true; };
  }, []);
  const bizLimit = planInfo
    ? (planInfo.is_admin || planInfo.plan === 'reviewer' ? Infinity
       : planInfo.plan === 'business_max' ? 3
       : planInfo.plan === 'business' ? 1
       : 0)
    : Infinity;
  const reachedBizLimit = businesses.length >= bizLimit;

  const startRename = (i, e) => {
    e.stopPropagation();
    setRenamingIdx(i);
    setRenameValue(businesses[i].name);
  };

  const confirmRename = async (i) => {
    if (!renameValue.trim()) { setRenamingIdx(-1); return; }
    const target = businesses[i];
    if (!target) { setRenamingIdx(-1); return; }
    const newName = renameValue.trim();
    // Optimistic update
    const next = businesses.map((b, idx) => idx === i ? { ...b, name: newName } : b);
    persistBusinesses(next);
    setRenamingIdx(-1);
    try {
      await authFetch(`/api/businesses/${encodeURIComponent(target.id)}`, {
        method: 'PATCH',
        body: JSON.stringify({ name: newName }),
      });
    } catch (e) {
      console.warn('[businesses] rename failed:', e.message);
    }
  };

  // Two-step delete with inline confirmation. The trash icon shows on hover; clicking it
  // first arms a "¿Borrar?" confirmation, second click commits. Backend rejects deleting
  // the LAST remaining business (the user always has at least one).
  const [confirmDeleteIdx, setConfirmDeleteIdx] = React.useState(-1);
  const armDelete = (i, e) => {
    e.stopPropagation();
    setConfirmDeleteIdx(i);
    // Auto-cancel the arm after 4s if the user moves away.
    setTimeout(() => setConfirmDeleteIdx(prev => (prev === i ? -1 : prev)), 4000);
  };
  const commitDelete = async (i, e) => {
    e.stopPropagation();
    const target = businesses[i];
    if (!target) { setConfirmDeleteIdx(-1); return; }
    if (businesses.length <= 1) {
      setConfirmDeleteIdx(-1);
      alert(tt.cantDeleteLast || 'No puedes borrar el último negocio');
      return;
    }
    // Optimistic remove. If the deleted one was the active biz, fall back to index 0.
    const wasActive = i === currentBiz;
    const next = businesses.filter((_, idx) => idx !== i);
    persistBusinesses(next);
    let newIdx = currentBiz;
    if (wasActive) {
      newIdx = 0;
      setCurrentBiz(0);
      const fallback = next[0];
      if (fallback) {
        localStorage.setItem('nw_active_biz_id', String(fallback.id));
        if (onBizChange) onBizChange(fallback.id);
      }
    } else if (i < currentBiz) {
      // Shift current biz index left because we removed one to its left
      setCurrentBiz(currentBiz - 1);
    }
    setConfirmDeleteIdx(-1);
    try {
      const res = await authFetch(`/api/businesses/${encodeURIComponent(target.id)}`, { method: 'DELETE' });
      if (!res.ok) {
        const err = await res.json().catch(() => ({}));
        // Roll back optimistic change.
        persistBusinesses(businesses);
        setCurrentBiz(currentBiz);
        alert(err.error || (tt.deleteFailed || 'No se pudo borrar el negocio'));
      }
    } catch (e) {
      console.warn('[businesses] delete failed:', e.message);
      persistBusinesses(businesses);
      setCurrentBiz(currentBiz);
    }
  };

  const biz = businesses[currentBiz] || businesses[0];

  // Close the business dropdown when clicking outside of it. Previously the menu stayed open
  // and blocked clicks on other UI elements until the user re-clicked the trigger.
  const bizWrapperRef = React.useRef(null);
  React.useEffect(() => {
    if (!open) return;
    const handler = (e) => {
      if (bizWrapperRef.current && !bizWrapperRef.current.contains(e.target)) {
        setOpen(false);
      }
    };
    document.addEventListener('mousedown', handler);
    return () => document.removeEventListener('mousedown', handler);
  }, [open]);

  return (
    <div ref={bizWrapperRef} className="nx-top-bar" style={{ position:'fixed', top:'16px', left:'20px', zIndex:50, maxWidth:'560px' }}>
      {/* Main pill */}
      <div style={{
        display:'flex', alignItems:'center', gap:'8px',
        background:'rgba(10,10,12,0.82)', backdropFilter:'blur(24px)',
        border:'1px solid rgba(255,255,255,0.08)',
        borderRadius:'16px', padding:'9px 14px',
        boxShadow:'0 4px 24px rgba(0,0,0,0.35), inset 0 1px 0 rgba(255,255,255,0.04)',
      }}>
        <img src="/icono500x500.png" alt="NexWall" style={{ width:24, height:24, borderRadius:6, objectFit:'cover', flexShrink:0 }}/>
        <span className="nx-brand-text" style={{ fontSize:'12px', fontWeight:600, color:NX.text, letterSpacing:'-0.01em', whiteSpace:'nowrap' }}>NexWall Corp AI</span>

        {/* Divider */}
        <div className="nx-brand-divider" style={{ width:'1px', height:'16px', background:NX.border, flexShrink:0 }}/>

        {/* Business selector button */}
        <button onClick={() => setOpen(!open)} className="nx-biz-trigger" style={{
          display:'flex', alignItems:'center', gap:'6px', background:'transparent',
          border:'none', cursor:'pointer', padding:'3px 6px',
          borderRadius:'8px', transition:'background 0.15s', minWidth: 0, flex: '1 1 auto',
        }}
          onMouseEnter={e => e.currentTarget.style.background = NX.bg3}
          onMouseLeave={e => e.currentTarget.style.background = 'transparent'}>
          <span style={{ fontSize:'14px', flexShrink:0 }}>{biz.emoji}</span>
          <span className="nx-biz-name" style={{ fontSize:'12px', color:NX.text, fontWeight:500, maxWidth:'220px', overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>{biz.name}</span>
          <svg width="12" height="12" viewBox="0 0 12 12" fill="none" style={{ transform: open ? 'rotate(180deg)' : 'rotate(0)', transition:'transform 0.2s', flexShrink:0 }}>
            <path d="M3 4.5l3 3 3-3" stroke={NX.muted} strokeWidth="1.3" strokeLinecap="round"/>
          </svg>
        </button>

        <div style={{ marginLeft:'auto', display:'flex', alignItems:'center', gap:'6px', flexShrink:0 }}>
          <CreditsPill lang={lang} onClick={onCreditsClick} />
          <div style={{ width:'26px', height:'26px', borderRadius:'50%', background:'linear-gradient(135deg, #8b5cf6, #a78bfa)', display:'flex', alignItems:'center', justifyContent:'center', fontSize:'10px', fontWeight:700, color:'#fff', flexShrink:0, title: currentUser?.email }}>
            {currentUser?.name ? currentUser.name[0].toUpperCase() : currentUser?.email?.[0]?.toUpperCase() || 'U'}
          </div>
          <a href="/landing" className="nx-home-btn" title={lang==='es' ? 'Ir a inicio' : 'Go to home'} style={{ background:'transparent', border:`1px solid ${NX.border}`, borderRadius:'6px', color:NX.muted, cursor:'pointer', padding:'3px 6px', transition:'all 0.15s', display:'flex', alignItems:'center', justifyContent:'center', textDecoration:'none' }}
            onMouseEnter={e=>e.currentTarget.style.color=NX.text}
            onMouseLeave={e=>e.currentTarget.style.color=NX.muted}>
            <svg width="12" height="12" viewBox="0 0 16 16" fill="none">
              <path d="M2 7l6-5 6 5v7a1 1 0 01-1 1h-3v-4H6v4H3a1 1 0 01-1-1V7z" stroke="currentColor" strokeWidth="1.3" strokeLinecap="round" strokeLinejoin="round"/>
            </svg>
          </a>
          {onToggleLang && (
            <button onClick={onToggleLang} className="nx-lang-btn" style={{ background:'transparent', border:`1px solid ${NX.border}`, borderRadius:'6px', color:NX.muted, cursor:'pointer', padding:'2px 6px', fontSize:'10px', fontFamily:"'DM Mono',monospace", letterSpacing:'0.05em', transition:'all 0.15s' }}
              onMouseEnter={e=>e.currentTarget.style.color=NX.text}
              onMouseLeave={e=>e.currentTarget.style.color=NX.muted}>
              {tt.langToggle || 'EN'}
            </button>
          )}
          <button onClick={onMobileMenu} className="nx-mobile-menu-btn" style={{ background:'none', border:'none', color:NX.muted, cursor:'pointer', display:'none', padding:'4px' }}>
            <svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M2 4h12M2 8h12M2 12h12" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round"/></svg>
          </button>
        </div>
      </div>

      {/* Dropdown */}
      {open && (
        <div style={{
          position:'absolute', top:'calc(100% + 8px)', left:0, minWidth:'240px',
          background:'rgba(12,12,14,0.96)', backdropFilter:'blur(24px)',
          border:`1px solid ${NX.border}`, borderRadius:'14px',
          padding:'8px', boxShadow:'0 8px 32px rgba(0,0,0,0.5)',
          animation:'modalIn 0.15s ease',
        }}>
          <div style={{ fontSize:'9px', color:NX.muted2, fontFamily:"'DM Mono',monospace", letterSpacing:'0.12em', padding:'4px 10px 8px', textTransform:'uppercase' }}>{tt.myBusinesses || 'Mis negocios'}</div>
          {businesses.map((b, i) => (
            <div key={b.id} style={{ position:'relative' }}
              onMouseEnter={e => {
                e.currentTarget.querySelectorAll('.biz-action-btn').forEach(el => { el.style.opacity = '1'; });
              }}
              onMouseLeave={e => {
                e.currentTarget.querySelectorAll('.biz-action-btn').forEach(el => { el.style.opacity = '0'; });
              }}>
              {renamingIdx === i ? (
                <div style={{ display:'flex', alignItems:'center', gap:'6px', padding:'6px 10px' }}>
                  <span style={{ fontSize:'16px' }}>{b.emoji}</span>
                  <input
                    autoFocus
                    value={renameValue}
                    onChange={e => setRenameValue(e.target.value)}
                    onKeyDown={e => { if(e.key==='Enter') confirmRename(i); if(e.key==='Escape') setRenamingIdx(-1); }}
                    onBlur={() => confirmRename(i)}
                    style={{ flex:1, background:NX.bg3, border:`1px solid ${NX.accent}`, borderRadius:'7px', color:NX.text, fontSize:'13px', padding:'4px 8px', fontFamily:"'DM Sans',sans-serif", outline:'none' }}
                  />
                </div>
              ) : (
                <button onClick={() => { setBiz(i); setOpen(false); }}
                  style={{
                    display:'flex', alignItems:'center', gap:'10px', width:'100%',
                    padding:'9px 10px', borderRadius:'9px', border:'none', cursor:'pointer',
                    background: i===currentBiz ? NX.bg4 : 'transparent',
                    transition:'background 0.12s', textAlign:'left',
                  }}
                  onMouseEnter={e => { if(i!==currentBiz) e.currentTarget.style.background=NX.bg3; }}
                  onMouseLeave={e => { if(i!==currentBiz) e.currentTarget.style.background='transparent'; }}>
                  <span style={{ fontSize:'16px', lineHeight:1 }}>{b.emoji}</span>
                  <span style={{ fontSize:'13px', color: i===currentBiz ? NX.text : NX.muted, fontWeight: i===currentBiz ? 500 : 400 }}>{b.name}</span>
                  <div style={{ marginLeft:'auto', display:'flex', alignItems:'center', gap:'4px' }}>
                    {i===currentBiz && <svg width="12" height="12" viewBox="0 0 12 12" fill="none"><path d="M2 6l3 3 5-6" stroke={NX.accent} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/></svg>}
                    <button className="biz-action-btn biz-rename-btn" onClick={e => startRename(i, e)}
                      title={tt.rename || 'Renombrar'}
                      style={{ opacity:0, transition:'opacity 0.15s', background:'transparent', border:`1px solid ${NX.border}`, borderRadius:'5px', color:NX.muted, cursor:'pointer', padding:'2px 5px', display:'flex', alignItems:'center', justifyContent:'center' }}>
                      <svg width="10" height="10" viewBox="0 0 16 16" fill="none"><path d="M11.5 2.5a1.5 1.5 0 012.121 2.121l-8.5 8.5-2.829.707.707-2.828 8.5-8.5z" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round"/></svg>
                    </button>
                    {/* Delete — two-click confirm. First click arms (red bg), second commits.
                        Disabled visually when this is the last business (Meta needs ≥1). */}
                    {confirmDeleteIdx === i ? (
                      <button className="biz-action-btn biz-delete-btn" onClick={e => commitDelete(i, e)}
                        title={tt.confirmDelete || '¿Borrar negocio? Click para confirmar'}
                        style={{ opacity:1, background:'rgba(248,113,113,0.18)', border:`1px solid ${NX.danger}`, borderRadius:'5px', color: NX.danger, cursor:'pointer', padding:'2px 7px', fontSize:'9px', fontWeight:700, fontFamily:"'DM Sans',sans-serif", letterSpacing:'0.05em' }}>
                        {tt.confirm || '¿Borrar?'}
                      </button>
                    ) : (
                      <button className="biz-action-btn biz-delete-btn" onClick={e => armDelete(i, e)}
                        disabled={businesses.length <= 1}
                        title={businesses.length <= 1 ? (tt.cantDeleteLast || 'No puedes borrar el último negocio') : (tt.delete || 'Borrar negocio')}
                        style={{ opacity:0, transition:'opacity 0.15s', background:'transparent', border:`1px solid ${NX.border}`, borderRadius:'5px', color: businesses.length <= 1 ? NX.muted2 : NX.muted, cursor: businesses.length <= 1 ? 'not-allowed' : 'pointer', padding:'2px 5px', display:'flex', alignItems:'center', justifyContent:'center' }}
                        onMouseEnter={e => { if (businesses.length > 1) e.currentTarget.style.color = NX.danger; }}
                        onMouseLeave={e => { e.currentTarget.style.color = businesses.length <= 1 ? NX.muted2 : NX.muted; }}>
                        <svg width="10" height="10" viewBox="0 0 16 16" fill="none"><path d="M3 4h10M6 4V2.5A.5.5 0 016.5 2h3a.5.5 0 01.5.5V4M4.5 4l.5 9a1 1 0 001 1h4a1 1 0 001-1l.5-9" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round"/></svg>
                      </button>
                    )}
                  </div>
                </button>
              )}
            </div>
          ))}

          <div style={{ height:'1px', background:NX.border, margin:'6px 0' }}/>

          {/* Contador del límite del plan: "2 / 3 negocios" cuando aplica. */}
          {planInfo && bizLimit !== Infinity && (
            <div style={{ padding:'4px 10px 2px', fontSize:'10px', color: reachedBizLimit ? NX.warning : NX.muted, fontFamily:"'DM Mono',monospace", letterSpacing:'0.05em' }}>
              {businesses.length} / {bizLimit} {lang === 'en' ? 'businesses' : 'negocios'}
              {reachedBizLimit && (lang === 'en' ? ' · upgrade for more' : ' · upgrade para más')}
            </div>
          )}
          {adding ? (
            <div style={{ padding:'6px 8px', display:'flex', gap:'6px' }}>
              <input autoFocus value={newName} onChange={e => setNewName(e.target.value)}
                onKeyDown={e => { if(e.key==='Enter') addBusiness(); if(e.key==='Escape') { setAdding(false); setNewName(''); } }}
                placeholder={tt.placeholder || 'Nombre del negocio...'}
                style={{ flex:1, background:NX.bg4, border:`1px solid ${NX.border2}`, borderRadius:'7px', color:NX.text, fontSize:'12px', padding:'6px 10px', fontFamily:"'DM Sans',sans-serif", outline:'none' }}/>
              <button onClick={addBusiness} style={{ background:NX.accent, border:'none', borderRadius:'7px', color:'#fff', cursor:'pointer', padding:'6px 10px', fontSize:'12px', fontWeight:500 }}>+</button>
            </div>
          ) : (
            <button
              disabled={reachedBizLimit}
              onClick={() => {
                if (reachedBizLimit) {
                  alert(lang === 'en'
                    ? `Your plan allows ${bizLimit} business${bizLimit === 1 ? '' : 'es'}. Upgrade to Business MAX for more.`
                    : `Tu plan permite ${bizLimit} negocio${bizLimit === 1 ? '' : 's'}. Cambia a Business MAX para vincular más.`);
                  return;
                }
                setAdding(true);
              }}
              style={{ display:'flex', alignItems:'center', gap:'8px', width:'100%', padding:'9px 10px', borderRadius:'9px', border:'none', cursor: reachedBizLimit ? 'not-allowed' : 'pointer', background:'transparent', transition:'background 0.12s', color: reachedBizLimit ? NX.muted : NX.accent, opacity: reachedBizLimit ? 0.55 : 1 }}
              onMouseEnter={e => { if (!reachedBizLimit) e.currentTarget.style.background=NX.bg3; }}
              onMouseLeave={e => e.currentTarget.style.background='transparent'}>
              <svg width="14" height="14" viewBox="0 0 14 14" fill="none">
                {reachedBizLimit
                  ? <path d="M4 7V5a3 3 0 016 0v2M2 7h10v6H2V7z" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round"/>
                  : <path d="M7 1v12M1 7h12" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round"/>}
              </svg>
              <span style={{ fontSize:'12px', fontWeight:500 }}>
                {reachedBizLimit
                  ? (lang === 'en' ? 'Limit reached' : 'Límite alcanzado')
                  : (tt.addBusiness || 'Agregar negocio')}
              </span>
            </button>
          )}

          {onLogout && <>
            <div style={{ height:'1px', background:NX.border, margin:'6px 0' }}/>
            {currentUser && <div style={{ fontSize:'11px', color:NX.muted, padding:'2px 10px 6px', overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>{currentUser.email}</div>}
            <button onClick={onLogout}
              style={{ display:'flex', alignItems:'center', gap:'8px', width:'100%', padding:'9px 10px', borderRadius:'9px', border:'none', cursor:'pointer', background:'transparent', transition:'background 0.12s', color:NX.danger }}
              onMouseEnter={e => e.currentTarget.style.background='rgba(248,113,113,0.08)'}
              onMouseLeave={e => e.currentTarget.style.background='transparent'}>
              <svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M5 2H2a1 1 0 00-1 1v8a1 1 0 001 1h3M9 10l3-3-3-3M13 7H5" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round"/></svg>
              <span style={{ fontSize:'12px', fontWeight:500 }}>{tt.logout || 'Cerrar sesión'}</span>
            </button>
          </>}
        </div>
      )}
    </div>
  );
};

// ─── NEBULA BRAIN (ambient "thinking" animation; replaces BottomAITracker robot) ───
// Exported as BottomAITracker to avoid changing every call site. The old robot was playful and
// cursor-tracking; this new one is contemplative — a brain (NexWall "N" icon) suspended in a
// slowly-rotating aurora nebula, pulsing softly as if studying. No mouse tracking.
// Backup of previous robot implementation: git tag `backup-pre-phase1-dashboard`.
const BottomAITracker = () => {
  // Pure CSS animation — no JS loop, no listeners, near-zero CPU.
  const styleTag = `
    @keyframes nbPulse { 0%,100% { transform:scale(1); opacity:0.92 } 50% { transform:scale(1.08); opacity:1 } }
    @keyframes nbRot1 { from { transform:rotate(0deg) } to { transform:rotate(360deg) } }
    @keyframes nbRot2 { from { transform:rotate(360deg) } to { transform:rotate(0deg) } }
    @keyframes nbShift { 0%,100% { transform:translate(0,0) scale(1) } 33% { transform:translate(-6px,-3px) scale(1.04) } 66% { transform:translate(5px,-2px) scale(0.97) } }
    @keyframes nbShift2 { 0%,100% { transform:translate(0,0) scale(1) } 50% { transform:translate(4px,3px) scale(1.06) } }
    @keyframes nbGlow { 0%,100% { opacity:0.4 } 50% { opacity:0.8 } }
    @keyframes nbOrbit { from { transform:rotate(0deg) translateX(44px) rotate(0deg) } to { transform:rotate(360deg) translateX(44px) rotate(-360deg) } }
    @keyframes nbOrbitR { from { transform:rotate(360deg) translateX(34px) rotate(-360deg) } to { transform:rotate(0deg) translateX(34px) rotate(0deg) } }
    @keyframes nbThink { 0% { opacity:0; transform:translateY(0) scale(0.6) } 50% { opacity:0.8 } 100% { opacity:0; transform:translateY(-28px) scale(1.1) } }
  `;
  return (
    <div className="nx-nebula-brain" style={{
      position: 'fixed', bottom: '20px', left: '50%', transform: 'translateX(-50%)',
      zIndex: 39, pointerEvents: 'none', width: '160px', height: '160px',
    }}>
      <style>{styleTag}</style>

      {/* Aurora nebula — two radial blobs drifting to create shimmer */}
      <div style={{
        position: 'absolute', inset: '-30px', borderRadius: '50%',
        background: 'radial-gradient(circle at 30% 40%, rgba(139,92,246,0.35) 0%, rgba(139,92,246,0) 55%), radial-gradient(circle at 70% 60%, rgba(139,92,246,0.28) 0%, rgba(139,92,246,0) 60%), radial-gradient(circle at 50% 50%, rgba(236,72,153,0.18) 0%, rgba(236,72,153,0) 65%)',
        filter: 'blur(18px)',
        animation: 'nbShift 11s ease-in-out infinite',
      }}/>
      <div style={{
        position: 'absolute', inset: '-10px', borderRadius: '50%',
        background: 'radial-gradient(circle at 60% 40%, rgba(167,139,250,0.22), rgba(167,139,250,0) 60%)',
        filter: 'blur(12px)',
        animation: 'nbShift2 7s ease-in-out infinite',
      }}/>

      {/* Outer rotating ring (dashed) */}
      <svg viewBox="0 0 160 160" style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', animation: 'nbRot1 22s linear infinite' }}>
        <circle cx="80" cy="80" r="62" fill="none" stroke="rgba(139,92,246,0.35)" strokeWidth="0.8" strokeDasharray="2 8"/>
      </svg>
      {/* Inner counter-rotating ring */}
      <svg viewBox="0 0 160 160" style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', animation: 'nbRot2 14s linear infinite' }}>
        <circle cx="80" cy="80" r="48" fill="none" stroke="rgba(167,139,250,0.25)" strokeWidth="0.6" strokeDasharray="1 6"/>
      </svg>

      {/* Orbiting energy dots — emblem of "thinking" */}
      <div style={{ position: 'absolute', top: '50%', left: '50%', width: '0', height: '0' }}>
        <div style={{ position: 'absolute', width: '6px', height: '6px', borderRadius: '50%', background: '#a78bfa', boxShadow: '0 0 10px 2px rgba(167,139,250,0.6)', marginTop: '-3px', marginLeft: '-3px', animation: 'nbOrbit 6s linear infinite' }}/>
        <div style={{ position: 'absolute', width: '4px', height: '4px', borderRadius: '50%', background: '#8b5cf6', boxShadow: '0 0 8px 2px rgba(139,92,246,0.5)', marginTop: '-2px', marginLeft: '-2px', animation: 'nbOrbitR 4.5s linear infinite' }}/>
        <div style={{ position: 'absolute', width: '3px', height: '3px', borderRadius: '50%', background: '#ec4899', boxShadow: '0 0 6px 1px rgba(236,72,153,0.45)', marginTop: '-1.5px', marginLeft: '-1.5px', animation: 'nbOrbit 9s linear infinite 1s' }}/>
      </div>

      {/* Ascending "thinking wisps" */}
      <div style={{ position: 'absolute', top: '20%', left: '35%', width: '5px', height: '5px', borderRadius: '50%', background: 'rgba(167,139,250,0.8)', filter: 'blur(1px)', animation: 'nbThink 3.2s ease-out infinite' }}/>
      <div style={{ position: 'absolute', top: '15%', left: '60%', width: '4px', height: '4px', borderRadius: '50%', background: 'rgba(139,92,246,0.7)', filter: 'blur(1px)', animation: 'nbThink 4.1s ease-out infinite 1.2s' }}/>
      <div style={{ position: 'absolute', top: '25%', left: '50%', width: '3px', height: '3px', borderRadius: '50%', background: 'rgba(236,72,153,0.65)', filter: 'blur(1px)', animation: 'nbThink 3.8s ease-out infinite 2.4s' }}/>

      {/* Central — the NexWall mascot, eyes track the mouse for an alive feel */}
      <div style={{
        position: 'absolute', inset: 0,
        display: 'flex', alignItems: 'center', justifyContent: 'center',
      }}>
        <div style={{
          position: 'absolute', width: '88px', height: '88px', borderRadius: '50%',
          background: 'radial-gradient(circle, rgba(139,92,246,0.5) 0%, rgba(139,92,246,0) 70%)',
          filter: 'blur(6px)',
          animation: 'nbGlow 2.4s ease-in-out infinite',
        }}/>
        <Mascot size={84}/>
      </div>
    </div>
  );
};

// ─── AI PROCESSING ANIMATION ───
// `steps`, when provided, replaces the static "Procesando..." with a checklist that ticks
// off scripted phases on a rolling timer. Gives the user something to watch while the API
// chugs (otherwise this screen sits frozen for 30-60s and feels broken).
//
// Each entry in `steps` is a plain string. The component cycles them at a steady pace,
// promoting the active item to the next as it goes. The LAST item is held with a spinner
// (never auto-checked) because the real "done" signal comes from the parent unmounting
// the component — we don't know exactly when generation finishes.
const AiProcessing = ({ label='Procesando...', sublabel='La IA está trabajando', steps=null, stepDurations=null }) => {
  const useSteps = Array.isArray(steps) && steps.length > 0;
  const [activeIdx, setActiveIdx] = React.useState(0);

  React.useEffect(() => {
    if (!useSteps) return;
    // Si stepDurations viene custom, lo usamos (cada paso con su propio timing —
    // útil cuando el backend tiene un paso obviamente más largo, p.ej. generar
    // imágenes ~60-180s). Si no, default uniforme 3.5s.
    // El último paso NUNCA se auto-promueve — queda activo hasta que el caller desmonta.
    const durations = Array.isArray(stepDurations) && stepDurations.length === steps.length
      ? stepDurations
      : steps.map(() => 3500);
    let cancelled = false;
    const timers = [];
    const scheduleNext = (idx) => {
      if (cancelled || idx >= steps.length - 1) return;
      const t = setTimeout(() => {
        if (cancelled) return;
        setActiveIdx(idx + 1);
        scheduleNext(idx + 1);
      }, durations[idx]);
      timers.push(t);
    };
    scheduleNext(0);
    return () => { cancelled = true; timers.forEach(clearTimeout); };
  }, [useSteps, steps?.length, stepDurations?.join('|')]);

  return (
  <div style={{ display:'flex', flexDirection:'column', alignItems:'center', justifyContent:'center', height:'100%', gap: useSteps ? '20px' : '28px', padding:'40px' }}>
    <div style={{ position:'relative', width:'180px', height:'180px', display:'flex', alignItems:'center', justifyContent:'center' }}>
      {/* nebula blobs */}
      <div style={{ position:'absolute', width:'160px', height:'160px', borderRadius:'50%', background:'radial-gradient(circle, #8b5cf644 0%, #8b5cf622 50%, transparent 70%)', animation:'nebulaGlow 2.4s ease-in-out infinite' }}/>
      <div style={{ position:'absolute', width:'200px', height:'200px', borderRadius:'50%', background:'radial-gradient(circle, #8b5cf633 0%, #8b5cf611 60%, transparent 80%)', animation:'nebulaGlow2 3.1s ease-in-out infinite' }}/>
      {/* outer dashed rings */}
      <svg viewBox="0 0 180 180" fill="none" style={{ position:'absolute', width:'180px', height:'180px' }}>
        <circle cx="90" cy="90" r="82" stroke="#8b5cf6" strokeWidth="0.8" strokeDasharray="4 10" opacity="0.35"
          style={{ animation:'spin 12s linear infinite', transformOrigin:'90px 90px' }}/>
        <circle cx="90" cy="90" r="68" stroke="#8b5cf6" strokeWidth="0.6" strokeDasharray="2 14" opacity="0.25"
          style={{ animation:'spinR 8s linear infinite', transformOrigin:'90px 90px' }}/>
        <circle cx="90" cy="90" r="54" stroke="#8b5cf6" strokeWidth="0.5" strokeDasharray="6 6" opacity="0.15"
          style={{ animation:'spin 18s linear infinite', transformOrigin:'90px 90px' }}/>
      </svg>
      {/* orbiting energy dots */}
      <div style={{ position:'absolute', width:'8px', height:'8px', borderRadius:'50%', background:'#8b5cf6', boxShadow:'0 0 8px 3px #8b5cf6bb', animation:'energyOrbit 3s linear infinite', top:'50%', left:'50%', marginTop:'-4px', marginLeft:'-4px' }}/>
      <div style={{ position:'absolute', width:'6px', height:'6px', borderRadius:'50%', background:'#8b5cf6', boxShadow:'0 0 6px 3px #8b5cf6aa', animation:'energyOrbit2 4.5s linear infinite', top:'50%', left:'50%', marginTop:'-3px', marginLeft:'-3px' }}/>
      <div style={{ position:'absolute', width:'5px', height:'5px', borderRadius:'50%', background:'#a78bfa', boxShadow:'0 0 5px 2px #a78bfa88', animation:'energyOrbit3 6s linear infinite', top:'50%', left:'50%', marginTop:'-2.5px', marginLeft:'-2.5px' }}/>
      {/* the NexWall mascot — alive while processing */}
      <Mascot size={120} animate={false}/>
    </div>
    <div style={{ textAlign:'center' }}>
      <div style={{ fontSize:'17px', fontWeight:500, color:NX.text, marginBottom:'6px' }}>{label}</div>
      <div style={{ fontSize:'13px', color:NX.muted }}>{sublabel}</div>
    </div>

    {useSteps ? (
      // Dynamic checklist — completed steps glow violet with a green check, active step
      // pulses with a spinner, upcoming steps are dim. Animates each line in for kinetic feel.
      <div style={{ width:'100%', maxWidth:'360px', display:'flex', flexDirection:'column', gap:'10px' }}>
        {steps.map((s, i) => {
          const done = i < activeIdx;
          const active = i === activeIdx;
          if (i > activeIdx + 1) return null; // only render up-to-next so the list grows visibly
          return (
            <div key={i}
              style={{
                display:'flex', alignItems:'center', gap:'10px',
                opacity: done ? 1 : active ? 1 : 0.45,
                transform: active ? 'translateX(0)' : done ? 'translateX(0)' : 'translateX(-4px)',
                transition: 'opacity 0.35s ease, transform 0.35s ease',
                animation: i === activeIdx ? 'fadeSlide 0.45s ease' : undefined,
              }}>
              <div style={{
                width:'18px', height:'18px', borderRadius:'50%', flexShrink:0,
                background: done ? 'rgba(52,211,153,0.16)' : active ? 'rgba(139,92,246,0.18)' : 'rgba(139,92,246,0.08)',
                border:`1.5px solid ${done ? NX.success : active ? NX.accent : 'rgba(139,92,246,0.28)'}`,
                display:'flex', alignItems:'center', justifyContent:'center',
                boxShadow: active ? '0 0 12px rgba(139,92,246,0.45)' : 'none',
              }}>
                {done
                  ? <svg width="10" height="10" viewBox="0 0 14 14" fill="none"><path d="M2.5 7l3 3L11.5 4" stroke={NX.success} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/></svg>
                  : active
                    ? <span style={{ display:'inline-block', width:'8px', height:'8px', border:`1.5px solid ${NX.accent}`, borderTopColor:'transparent', borderRadius:'50%', animation:'spin 0.7s linear infinite' }}/>
                    : <span style={{ width:'4px', height:'4px', borderRadius:'50%', background:'rgba(139,92,246,0.5)' }}/>}
              </div>
              <div style={{ fontSize:'13px', color: done ? NX.text : active ? NX.text : NX.muted, fontFamily:"'DM Sans',sans-serif", fontWeight: active ? 500 : 400, lineHeight:1.35 }}>
                {s}
              </div>
            </div>
          );
        })}
      </div>
    ) : (
      <div style={{ display:'flex', gap:'6px' }}>
        {[0,1,2].map(i=>(
          <div key={i} style={{ width:'6px', height:'6px', borderRadius:'50%', background:NX.accent, animation:`dotBounce 1.2s ${i*0.2}s ease-in-out infinite` }}/>
        ))}
      </div>
    )}
  </div>
  );
};

// ─── CURSOR FX — nebula glow + click ripple ───
const CursorFX = () => {
  const glowRef = React.useRef(null);
  const [ripples, setRipples] = React.useState([]);

  React.useEffect(() => {
    // Respect prefers-reduced-motion and skip on touch devices
    const reduce = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
    const isTouch = ('ontouchstart' in window) && window.matchMedia('(pointer: coarse)').matches;
    if (reduce || isTouch) return;

    let rafId;
    let targetX = window.innerWidth / 2;
    let targetY = window.innerHeight / 2;
    let currentX = targetX;
    let currentY = targetY;

    const onMove = (e) => { targetX = e.clientX; targetY = e.clientY; };
    const onClick = (e) => {
      const id = Date.now() + Math.random();
      setRipples(prev => [...prev, { id, x: e.clientX, y: e.clientY }]);
      setTimeout(() => setRipples(prev => prev.filter(r => r.id !== id)), 700);
    };
    const animate = () => {
      currentX += (targetX - currentX) * 0.18;
      currentY += (targetY - currentY) * 0.18;
      if (glowRef.current) glowRef.current.style.transform = `translate3d(${currentX - 120}px, ${currentY - 120}px, 0)`;
      rafId = requestAnimationFrame(animate);
    };
    window.addEventListener('mousemove', onMove, { passive: true });
    window.addEventListener('mousedown', onClick);
    animate();
    return () => {
      window.removeEventListener('mousemove', onMove);
      window.removeEventListener('mousedown', onClick);
      cancelAnimationFrame(rafId);
    };
  }, []);

  return (
    <>
      <div ref={glowRef} style={{
        position: 'fixed', top: 0, left: 0, width: '240px', height: '240px', pointerEvents: 'none', zIndex: 9998,
        background: 'radial-gradient(circle, rgba(139,92,246,0.18) 0%, rgba(139,92,246,0.10) 35%, transparent 70%)',
        filter: 'blur(12px)', mixBlendMode: 'screen', willChange: 'transform',
      }}/>
      {ripples.map(r => (
        <div key={r.id} style={{
          position: 'fixed', top: r.y - 30, left: r.x - 30, width: '60px', height: '60px', pointerEvents: 'none', zIndex: 9999,
          borderRadius: '50%',
          background: 'radial-gradient(circle, rgba(139,92,246,0.45) 0%, rgba(139,92,246,0.25) 40%, transparent 70%)',
          animation: 'cursorRipple 0.7s ease-out forwards', mixBlendMode: 'screen',
        }}/>
      ))}
      <style>{`
        @keyframes cursorRipple {
          0%   { transform: scale(0.3); opacity: 0.9; }
          60%  { opacity: 0.5; }
          100% { transform: scale(2.6); opacity: 0; }
        }
      `}</style>
    </>
  );
};

Object.assign(window, {
  NX, NxButton, NxCard, NxBadge, NxInput, NxProgress, NxModal, NxDivider,
  RobotIcon, RobotLogoIcon, Mascot, RightNav, TopBar, BottomAITracker, AiProcessing, CursorFX,
  // keep NxOrbIcon as alias
  NxOrbIcon: ({ size=32 }) => React.createElement(RobotIcon, { size }),
  Sidebar: ({ view, setView, stepsCompleted, mobileOpen, setMobileOpen }) => null, // no-op
});
