﻿// perfil.jsx — Tela de Perfil

// ─── Toast ────────────────────────────────────────────────────────────────────
function useToast() {
  const [toasts, setToasts] = React.useState([]);
  const add = React.useCallback((msg, type = 'success') => {
    const id = Date.now() + Math.random();
    setToasts(t => [...t, { id, msg, type }]);
    setTimeout(() => setToasts(t => t.filter(x => x.id !== id)), 3500);
  }, []);
  return { toasts, add };
}

function ToastStack({ toasts }) {
  if (!toasts.length) return null;
  return (
    <div style={{ position:'fixed', bottom:24, right:24, display:'flex', flexDirection:'column', gap:8, zIndex:9999, pointerEvents:'none' }}
         role="region" aria-label="Notificações" aria-live="polite">
      {toasts.map(t => (
        <div key={t.id} style={{
          display:'flex', alignItems:'center', gap:10,
          padding:'10px 16px',
          background: t.type==='error' ? '#1e1014' : '#0d1a12',
          border:`1px solid ${t.type==='error' ? 'rgba(239,68,68,0.4)' : 'rgba(52,211,153,0.4)'}`,
          borderRadius:10, boxShadow:'0 4px 24px rgba(0,0,0,0.45)',
          color: t.type==='error' ? '#f87171' : '#34d399',
          fontSize:13, fontWeight:500,
          animation:'fadeUp 200ms ease both',
        }}>
          {t.type==='error'
            ? <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><circle cx="12" cy="12" r="9"/><path d="M12 8v4M12 16h.01"/></svg>
            : <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><path d="M20 6L9 17l-5-5"/></svg>
          }
          {t.msg}
        </div>
      ))}
    </div>
  );
}

// ─── Password strength ────────────────────────────────────────────────────────
function passStrength(p) {
  const checks = {
    length: p.length >= 8,
    upper: /[A-Z]/.test(p),
    lower: /[a-z]/.test(p),
    number: /\d/.test(p),
    symbol: /[^A-Za-z0-9]/.test(p),
  };
  const u = window.HOROS_USER || {};
  const common = !!p && !!window.isCommonPassword
    && window.isCommonPassword(p, [u.email, u.user_metadata?.nome, u.user_metadata?.sobrenome]);
  const score = Object.values(checks).filter(Boolean).length;
  // Senha comum/previsível (password-policy.js) fica sempre "Muito fraca"
  return { score: common ? Math.min(score, 1) : score, checks, common };
}

// ─── Crop Modal ───────────────────────────────────────────────────────────────
const CROP_VSIZE = 300;

function CropModal({ src, onConfirm, onCancel }) {
  const [cs, setCs] = React.useState({ scale:1, offset:{x:0,y:0}, baseScale:1, nw:0, nh:0 });
  const imgRef = React.useRef();
  const dragging = React.useRef(false);
  const lastMouse = React.useRef({ x:0, y:0 });
  const lastTouchDist = React.useRef(null);
  const lastTouchPos = React.useRef(null);

  const clamp = (ox, oy, dw, dh) => ({
    x: Math.min(0, Math.max(CROP_VSIZE - dw, ox)),
    y: Math.min(0, Math.max(CROP_VSIZE - dh, oy)),
  });

  const onImgLoad = () => {
    const img = imgRef.current;
    const nw = img.naturalWidth, nh = img.naturalHeight;
    const bs = Math.max(CROP_VSIZE / nw, CROP_VSIZE / nh);
    const dw = nw * bs, dh = nh * bs;
    setCs({ scale:1, baseScale:bs, nw, nh, offset: clamp((CROP_VSIZE - dw)/2, (CROP_VSIZE - dh)/2, dw, dh) });
  };

  const zoomTo = (newScale, cx, cy) => {
    setCs(s => {
      const ns = Math.max(1, Math.min(4, newScale));
      const oldDW = s.nw * s.baseScale * s.scale;
      const oldDH = s.nh * s.baseScale * s.scale;
      const newDW = s.nw * s.baseScale * ns;
      const newDH = s.nh * s.baseScale * ns;
      const pivX = cx !== undefined ? cx : CROP_VSIZE / 2;
      const pivY = cy !== undefined ? cy : CROP_VSIZE / 2;
      const newOx = pivX - (pivX - s.offset.x) * (newDW / (oldDW || 1));
      const newOy = pivY - (pivY - s.offset.y) * (newDH / (oldDH || 1));
      return { ...s, scale:ns, offset: clamp(newOx, newOy, newDW, newDH) };
    });
  };

  const onMouseDown = (e) => {
    dragging.current = true;
    lastMouse.current = { x:e.clientX, y:e.clientY };
    e.preventDefault();
  };
  const onMouseMove = (e) => {
    if (!dragging.current) return;
    const dx = e.clientX - lastMouse.current.x;
    const dy = e.clientY - lastMouse.current.y;
    lastMouse.current = { x:e.clientX, y:e.clientY };
    setCs(s => {
      const dw = s.nw * s.baseScale * s.scale;
      const dh = s.nh * s.baseScale * s.scale;
      return { ...s, offset: clamp(s.offset.x + dx, s.offset.y + dy, dw, dh) };
    });
  };
  const onMouseUp = () => { dragging.current = false; };

  const onWheel = (e) => {
    e.preventDefault();
    const factor = e.deltaY > 0 ? 0.92 : 1.09;
    setCs(s => {
      const ns = Math.max(1, Math.min(4, s.scale * factor));
      const oldDW = s.nw * s.baseScale * s.scale;
      const oldDH = s.nh * s.baseScale * s.scale;
      const newDW = s.nw * s.baseScale * ns;
      const newDH = s.nh * s.baseScale * ns;
      const cx = CROP_VSIZE / 2, cy = CROP_VSIZE / 2;
      return { ...s, scale:ns, offset: clamp(
        cx - (cx - s.offset.x) * (newDW / (oldDW || 1)),
        cy - (cy - s.offset.y) * (newDH / (oldDH || 1)),
        newDW, newDH
      )};
    });
  };

  const onTouchStart = (e) => {
    if (e.touches.length === 2) {
      lastTouchDist.current = Math.hypot(
        e.touches[0].clientX - e.touches[1].clientX,
        e.touches[0].clientY - e.touches[1].clientY
      );
      lastTouchPos.current = null;
    } else {
      lastTouchPos.current = { x:e.touches[0].clientX, y:e.touches[0].clientY };
      lastTouchDist.current = null;
    }
  };
  const onTouchMove = (e) => {
    e.preventDefault();
    if (e.touches.length === 2 && lastTouchDist.current) {
      const dist = Math.hypot(
        e.touches[0].clientX - e.touches[1].clientX,
        e.touches[0].clientY - e.touches[1].clientY
      );
      const factor = dist / lastTouchDist.current;
      lastTouchDist.current = dist;
      setCs(s => {
        const ns = Math.max(1, Math.min(4, s.scale * factor));
        const oldDW = s.nw * s.baseScale * s.scale;
        const oldDH = s.nh * s.baseScale * s.scale;
        const newDW = s.nw * s.baseScale * ns;
        const newDH = s.nh * s.baseScale * ns;
        const cx = CROP_VSIZE / 2, cy = CROP_VSIZE / 2;
        return { ...s, scale:ns, offset: clamp(
          cx - (cx - s.offset.x) * (newDW / (oldDW || 1)),
          cy - (cy - s.offset.y) * (newDH / (oldDH || 1)),
          newDW, newDH
        )};
      });
    } else if (e.touches.length === 1 && lastTouchPos.current) {
      const dx = e.touches[0].clientX - lastTouchPos.current.x;
      const dy = e.touches[0].clientY - lastTouchPos.current.y;
      lastTouchPos.current = { x:e.touches[0].clientX, y:e.touches[0].clientY };
      setCs(s => {
        const dw = s.nw * s.baseScale * s.scale;
        const dh = s.nh * s.baseScale * s.scale;
        return { ...s, offset: clamp(s.offset.x + dx, s.offset.y + dy, dw, dh) };
      });
    }
  };

  const confirm = () => {
    const canvas = document.createElement('canvas');
    canvas.width = 256; canvas.height = 256;
    const ctx = canvas.getContext('2d');
    const { nw, nh, baseScale, scale, offset } = cs;
    const dispW = nw * baseScale * scale;
    const dispH = nh * baseScale * scale;
    ctx.drawImage(imgRef.current,
      -offset.x / dispW * nw, -offset.y / dispH * nh,
      CROP_VSIZE / dispW * nw, CROP_VSIZE / dispH * nh,
      0, 0, 256, 256
    );
    onConfirm(canvas.toDataURL('image/jpeg', 0.88));
  };

  const dispW = cs.nw * cs.baseScale * cs.scale;
  const dispH = cs.nh * cs.baseScale * cs.scale;

  return (
    <div
      style={{ position:'fixed', inset:0, background:'rgba(0,0,0,0.9)', zIndex:10000,
               display:'flex', flexDirection:'column', alignItems:'center', justifyContent:'center',
               gap:20, padding:24 }}
      onMouseMove={onMouseMove}
      onMouseUp={onMouseUp}
    >
      <div style={{ color:'#fff', fontSize:15, fontWeight:600, letterSpacing:0.1 }}>Posicionar foto</div>
      <div style={{ color:'rgba(255,255,255,0.4)', fontSize:12, marginTop:-14, textAlign:'center' }}>
        Arraste para reposicionar · scroll ou slider para zoom
      </div>

      <div
        style={{ width:CROP_VSIZE, height:CROP_VSIZE, borderRadius:'50%', overflow:'hidden',
                 position:'relative', cursor:'grab', userSelect:'none', touchAction:'none', flexShrink:0,
                 boxShadow:'0 0 0 3px #1F93FF, 0 0 0 9999px rgba(0,0,0,0.55)' }}
        onMouseDown={onMouseDown}
        onWheel={onWheel}
        onTouchStart={onTouchStart}
        onTouchMove={onTouchMove}
        onTouchEnd={() => { lastTouchDist.current = null; lastTouchPos.current = null; }}
      >
        <img ref={imgRef} src={src} onLoad={onImgLoad} draggable={false} alt=""
             style={{ position:'absolute', left:cs.offset.x, top:cs.offset.y,
                      width: dispW || 'auto', height: dispH || 'auto',
                      pointerEvents:'none', userSelect:'none' }} />
      </div>

      <div style={{ display:'flex', alignItems:'center', gap:10, width:CROP_VSIZE }}>
        <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="rgba(255,255,255,0.35)" strokeWidth="2">
          <circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/>
        </svg>
        <input type="range" min="1" max="4" step="0.01" value={cs.scale}
               onChange={e => {
                 const ns = parseFloat(e.target.value);
                 setCs(s => {
                   const oldDW = s.nw * s.baseScale * s.scale;
                   const oldDH = s.nh * s.baseScale * s.scale;
                   const newDW = s.nw * s.baseScale * ns;
                   const newDH = s.nh * s.baseScale * ns;
                   const cx = CROP_VSIZE / 2, cy = CROP_VSIZE / 2;
                   return { ...s, scale:ns, offset: clamp(
                     cx - (cx - s.offset.x) * (newDW / (oldDW || 1)),
                     cy - (cy - s.offset.y) * (newDH / (oldDH || 1)),
                     newDW, newDH
                   )};
                 });
               }}
               style={{ flex:1, accentColor:'#1F93FF', cursor:'pointer' }} />
        <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="rgba(255,255,255,0.35)" strokeWidth="2">
          <circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/>
          <line x1="11" y1="8" x2="11" y2="14"/><line x1="8" y1="11" x2="14" y2="11"/>
        </svg>
      </div>

      <div style={{ display:'flex', gap:12 }}>
        <button onClick={onCancel}
                style={{ padding:'9px 24px', borderRadius:8, border:'1px solid rgba(255,255,255,0.2)',
                         background:'transparent', color:'#fff', fontSize:13, cursor:'pointer', fontWeight:500 }}>
          Cancelar
        </button>
        <button onClick={confirm}
                style={{ padding:'9px 24px', borderRadius:8, border:'none',
                         background:'#1F93FF', color:'#fff', fontSize:13, fontWeight:600, cursor:'pointer' }}>
          Confirmar
        </button>
      </div>
    </div>
  );
}

// ─── Avatar ───────────────────────────────────────────────────────────────────
function AvatarUpload({ avatarUrl, initials, color, onChange, disabled }) {
  const [hover, setHover] = React.useState(false);
  const [loading, setLoading] = React.useState(false);
  const [err, setErr] = React.useState('');
  const [dragOver, setDragOver] = React.useState(false);
  const [cropSrc, setCropSrc] = React.useState(null);
  const fileRef = React.useRef();

  const processFile = (file) => {
    if (!file) return;
    if (!file.type.startsWith('image/')) { setErr('Formato inválido — use JPG, PNG ou WebP'); return; }
    if (file.size > 10 * 1024 * 1024) { setErr('Máx 10 MB'); return; }
    setErr(''); setLoading(true);
    const reader = new FileReader();
    reader.onload = (e) => { setCropSrc(e.target.result); setLoading(false); };
    reader.onerror = () => { setErr('Erro ao ler arquivo'); setLoading(false); };
    reader.readAsDataURL(file);
  };

  const onDrop = (e) => {
    e.preventDefault(); setDragOver(false);
    if (disabled) return;
    processFile(e.dataTransfer.files[0]);
  };

  return (
    <div style={{ position:'relative', display:'inline-block' }}>
      {cropSrc && (
        <CropModal
          src={cropSrc}
          onConfirm={(dataUrl) => { onChange(dataUrl); setCropSrc(null); if (fileRef.current) fileRef.current.value = ''; }}
          onCancel={() => { setCropSrc(null); if (fileRef.current) fileRef.current.value = ''; }}
        />
      )}

      <div
        className={`pf-av${dragOver ? ' pf-av--drag' : ''}`}
        onMouseEnter={() => setHover(true)}
        onMouseLeave={() => { setHover(false); setErr(''); }}
        onDragOver={(e) => { e.preventDefault(); if (!disabled) setDragOver(true); }}
        onDragLeave={() => setDragOver(false)}
        onDrop={onDrop}
        onClick={() => !disabled && fileRef.current?.click()}
        style={{ cursor: disabled ? 'default' : 'pointer' }}
        tabIndex={disabled ? -1 : 0}
        role="button"
        aria-label="Alterar foto de perfil"
        onKeyDown={(e) => e.key === 'Enter' && !disabled && fileRef.current?.click()}
      >
        {loading ? (
          <div className="pf-av-inner pf-av-initials" style={{ background:`linear-gradient(135deg, ${color}, ${color}88)` }}>
            <Skel circle h={28} style={{ background: 'rgba(255,255,255,.25)' }} />
          </div>
        ) : avatarUrl ? (
          <img className="pf-av-inner" src={avatarUrl} alt="Foto de perfil" style={{ objectFit:'cover' }} />
        ) : (
          <div className="pf-av-inner pf-av-initials" style={{ background:`linear-gradient(135deg, ${color}, ${color}88)` }}>
            {initials}
          </div>
        )}
        {!disabled && (hover || dragOver) && !loading && (
          <div className="pf-av-overlay" aria-hidden="true">
            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
            <span>Trocar foto</span>
          </div>
        )}
      </div>

      {avatarUrl && !disabled && (
        <button className="pf-av-remove" onClick={(e) => { e.stopPropagation(); onChange(''); }}
                title="Remover foto" aria-label="Remover foto de perfil">
          <svg width="9" height="9" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3"><path d="M18 6L6 18M6 6l12 12"/></svg>
        </button>
      )}

      {err && <div className="pf-av-err" role="alert">{err}</div>}
      <input ref={fileRef} type="file" accept="image/png,image/jpeg,image/webp"
             style={{ display:'none' }} aria-hidden="true"
             onChange={e => processFile(e.target.files[0])} />
    </div>
  );
}

// Plano da imobiliária para o selo do cabeçalho (sem plano pago → sem selo)
function planInfo(ws) {
  const live = ['trialing', 'active', 'past_due', 'unpaid', 'incomplete'].includes(ws?.plan_status);
  const label = live ? ({ pro: 'Pro', max: 'Max', team: 'Team', dist_10: 'Distribuição', dist_30: 'Distribuição', dist_unl: 'Distribuição' }[ws?.plan] || '') : '';
  return { planLabel: label };
}

// ─── Card wrapper ─────────────────────────────────────────────────────────────
function PCard({ title, icon, children, action, danger, span2 }) {
  return (
    <div className={`pf-card${danger ? ' pf-card--danger' : ''}${span2 ? ' pf-card--span2' : ''}`}>
      <div className="pf-card-head">
        <div className="pf-card-title">
          <Icon name={icon} size={14} style={{ color: danger ? 'var(--danger)' : 'var(--accent)' }} />
          <span>{title}</span>
        </div>
        {action && <div>{action}</div>}
      </div>
      <div className="pf-card-body">{children}</div>
    </div>
  );
}

// ─── Form primitives ──────────────────────────────────────────────────────────
function PField({ label, helper, error, children }) {
  return (
    <div className="pf-field">
      {label && <label className="pf-label">{label}</label>}
      {children}
      {error
        ? <span className="pf-helper pf-err" role="alert">{error}</span>
        : helper
        ? <span className="pf-helper">{helper}</span>
        : null}
    </div>
  );
}

function PInput({ value, onChange, placeholder, type='text', disabled, readOnly, onKeyDown }) {
  return (
    <div className={`pf-input-wrap${disabled || readOnly ? ' pf-input-wrap--ro' : ''}`}>
      <input className="pf-input" type={type} value={value} placeholder={placeholder}
             disabled={disabled} readOnly={readOnly} onChange={onChange} onKeyDown={onKeyDown}
             aria-readonly={readOnly || undefined} />
      {readOnly && (
        <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5"
             className="pf-ro-ico" aria-hidden="true">
          <rect x="5" y="11" width="14" height="10" rx="2"/><path d="M8 11V7a4 4 0 1 1 8 0v4"/>
        </svg>
      )}
    </div>
  );
}

function PTextarea({ value, onChange, placeholder, rows=3, disabled, maxLen }) {
  return (
    <div style={{ display:'flex', flexDirection:'column', gap:3 }}>
      <textarea className="pf-input pf-textarea" value={value} placeholder={placeholder}
                rows={rows} disabled={disabled} aria-multiline="true"
                onChange={e => onChange(maxLen ? e.target.value.slice(0, maxLen) : e.target.value)} />
      {maxLen && <span className="pf-charcount" aria-live="polite">{value.length}/{maxLen}</span>}
    </div>
  );
}

function PSwitch({ checked, onChange, disabled, id }) {
  return (
    <button id={id} type="button"
            className={`pf-switch${checked ? ' on' : ''}${disabled ? ' dis' : ''}`}
            role="switch" aria-checked={checked}
            onClick={() => !disabled && onChange(!checked)} disabled={disabled}>
      <span className="pf-switch-thumb" aria-hidden="true" />
    </button>
  );
}

function OptionGroup({ options, value, onChange }) {
  return (
    <div className="pf-optgroup" role="radiogroup">
      {options.map(o => (
        <button key={o.id} type="button"
                className={`pf-opt${value === o.id ? ' pf-opt--on' : ''}`}
                role="radio" aria-checked={value === o.id}
                onClick={() => onChange(o.id)}>
          {o.label}
        </button>
      ))}
    </div>
  );
}

// ─── PROFILE HEADER ───────────────────────────────────────────────────────────
function ProfileHeader({ profile, editing, editForm, onEditChange, onEdit, onSave, onCancel, saving, dirty, onAvatarChange, effectiveAvatarUrl }) {
  const AREA = { mcmv:'Imobiliário' };
  const created = profile.created_at
    ? new Date(profile.created_at).toLocaleDateString('pt-BR', { day:'2-digit', month:'long', year:'numeric' })
    : '—';

  return (
    <div className="pf-hero" role="banner">
      <div className="pf-hero-left">
        <AvatarUpload avatarUrl={effectiveAvatarUrl !== undefined ? effectiveAvatarUrl : profile.avatarUrl}
                      initials={profile.initials}
                      color={profile.color} onChange={onAvatarChange} disabled={!editing} />

        <div className="pf-hero-info">
          {editing ? (
            <div className="pf-hero-edit-row">
              <input className="pf-input pf-input--name" value={editForm.nome}
                     onChange={e => onEditChange('nome', e.target.value)}
                     placeholder="Nome" aria-label="Nome" />
              <input className="pf-input pf-input--name" value={editForm.sobrenome}
                     onChange={e => onEditChange('sobrenome', e.target.value)}
                     placeholder="Sobrenome" aria-label="Sobrenome" />
            </div>
          ) : (
            <h1 className="pf-hero-name">{profile.nome} {profile.sobrenome}</h1>
          )}

          <div className="pf-hero-meta">
            {editing ? (
              <input className="pf-input pf-input--sm" value={editForm.cargo}
                     onChange={e => onEditChange('cargo', e.target.value)}
                     placeholder="Cargo" aria-label="Cargo" />
            ) : (
              <>
                <span className="pf-hero-role">{editForm.cargo || profile.cargo || segTerm('Agent', 'Corretor')}</span>
                {profile.workspace_name && (
                  <><span className="pf-dot-sep" /><span className="pf-hero-role">{profile.workspace_name}</span></>
                )}
                {profile.area && <span className="pf-badge-seg">{AREA[profile.area] || profile.area}</span>}
              </>
            )}
          </div>

          <div className="pf-hero-contacts">
            <span className="pf-contact">
              <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" aria-hidden="true"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg>
              {profile.email}
            </span>
            {profile.telefone && (
              <span className="pf-contact">
                <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" aria-hidden="true"><path d="M5 4h3l2 5-2.5 1.5a11 11 0 0 0 6 6L15 14l5 2v3a2 2 0 0 1-2 2A14 14 0 0 1 4 7a2 2 0 0 1 1-3Z"/></svg>
                {profile.telefone}
              </span>
            )}
          </div>

          <div className="pf-hero-chips">
            {profile.planLabel && (
              <span className="pf-chip-plan" aria-label={`Plano ${profile.planLabel}`}>
                {profile.planLabel.toUpperCase()}
              </span>
            )}
            <span className="pf-chip-status">
              <span className="pf-online-dot" aria-hidden="true" />
              Online
            </span>
            <span className="pf-chip-info">Desde {created}</span>
            <span className="pf-chip-info">Acesso há poucos instantes</span>
          </div>
        </div>
      </div>

      <div className="pf-hero-actions">
        {editing ? (
          <>
            <button className="pf-ghost-btn" onClick={onCancel} disabled={saving}>Cancelar</button>
            <button className="btn-primary" onClick={onSave} disabled={!dirty || saving} aria-busy={saving}>
              {saving
                ? <><span className="pf-spin-sm" aria-hidden="true" /> Salvando…</>
                : <><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" aria-hidden="true"><path d="M20 6L9 17l-5-5"/></svg> Salvar</>
              }
            </button>
          </>
        ) : (
          <button className="btn-primary" onClick={onEdit}>
            <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" aria-hidden="true"><path d="m13 5 6 6M5 19l.5-4 12-12 4 4-12 12L5 19Z"/></svg>
            Editar Perfil
          </button>
        )}
      </div>
    </div>
  );
}

// ─── DADOS PESSOAIS ───────────────────────────────────────────────────────────
function DadosPessoaisCard({ profile, onUpdate, toast }) {
  const [form, setForm] = React.useState({
    nome: profile.nome || '',
    sobrenome: profile.sobrenome || '',
    telefone: profile.telefone || '',
    website: profile.website || '',
    cidade: profile.cidade || '',
    estado: profile.estado || '',
    bio: profile.bio || '',
  });
  const [dirty, setDirty] = React.useState(false);
  const [saving, setSaving] = React.useState(false);
  const [errors, setErrors] = React.useState({});

  const set = (k, v) => {
    setForm(f => ({ ...f, [k]: v }));
    setDirty(true);
    if (errors[k]) setErrors(e => ({ ...e, [k]: '' }));
  };

  const validate = () => {
    const e = {};
    if (!form.nome.trim()) e.nome = 'Nome é obrigatório';
    if (form.telefone) {
      const d = form.telefone.replace(/\D/g, '');
      if (d.length > 0 && (d.length < 10 || d.length > 11)) e.telefone = 'Formato inválido — use (XX) XXXXX-XXXX';
    }
    if (form.website && !/^https?:\/\/.+/.test(form.website)) e.website = 'URL inválida — deve começar com https://';
    return e;
  };

  const save = async () => {
    const e = validate();
    setErrors(e);
    if (Object.keys(e).length) return;
    setSaving(true);
    try {
      const uid = window.HOROS_USER?.id;
      await window.HOROS_SB.from('profiles').update({
        nome: form.nome,
        sobrenome: form.sobrenome,
      }).eq('id', uid);
      const extra = JSON.parse(localStorage.getItem(pfExtraKey()) || '{}');
      Object.assign(extra, { telefone: form.telefone, website: form.website, cidade: form.cidade, estado: form.estado, bio: form.bio });
      localStorage.setItem(pfExtraKey(), JSON.stringify(extra));
      setDirty(false);
      onUpdate(form);
      toast('Dados pessoais salvos');
    } catch { toast('Erro ao salvar dados', 'error'); }
    setSaving(false);
  };

  return (
    <PCard title="Dados Pessoais" icon="user"
      action={dirty && (
        <button className="btn-primary" style={{ fontSize:11.5, padding:'5px 11px' }}
                onClick={save} disabled={saving} aria-busy={saving}>
          {saving ? 'Salvando…' : 'Salvar'}
        </button>
      )}
    >
      <div className="pf-grid-2">
        <PField label="Nome" error={errors.nome}>
          <PInput value={form.nome} onChange={e => set('nome', e.target.value)} placeholder="Daniel" />
        </PField>
        <PField label="Sobrenome">
          <PInput value={form.sobrenome} onChange={e => set('sobrenome', e.target.value)} placeholder="Sena" />
        </PField>
      </div>

      <PField label="E-mail" helper="Para alterar o e-mail, uma confirmação será enviada para o novo endereço">
        <PInput value={profile.email || ''} readOnly />
      </PField>

      <PField label="Telefone" error={errors.telefone}>
        <PInput value={form.telefone} onChange={e => set('telefone', e.target.value)} placeholder="(11) 99999-9999" />
      </PField>

      <PField label="Website" error={errors.website}>
        <PInput value={form.website} onChange={e => set('website', e.target.value)} placeholder="https://seunome.com.br" />
      </PField>

      <div className="pf-grid-3">
        <PField label="Cidade">
          <PInput value={form.cidade} onChange={e => set('cidade', e.target.value)} placeholder="São Paulo" />
        </PField>
        <PField label="Estado">
          <PInput value={form.estado} onChange={e => set('estado', e.target.value)} placeholder="SP" />
        </PField>
        <PField label="País">
          <PInput value="Brasil" readOnly />
        </PField>
      </div>

      <PField label="Biografia">
        <PTextarea value={form.bio} onChange={v => set('bio', v)}
                   placeholder={segTerm('product') === 'imóvel' ? 'Corretor especializado em imóveis de alto padrão em São Paulo…' : `${segTerm('Agent', 'Consultor')} com 8 anos de experiência, atendo toda a região…`} maxLen={160} />
      </PField>
    </PCard>
  );
}

// ─── SEGURANÇA ────────────────────────────────────────────────────────────────
function SegurancaCard({ toast }) {
  const [form, setForm] = React.useState({ current:'', next:'', confirm:'' });
  const [show, setShow] = React.useState({ current:false, next:false, confirm:false });
  const [saving, setSaving] = React.useState(false);
  const [errors, setErrors] = React.useState({});
  const str = passStrength(form.next);

  const strColor = ['','#f87171','#f87171','#f5b544','#34d399','#34d399'][str.score];
  const strLabel = ['','Muito fraca','Fraca','Razoável','Boa','Forte'][str.score];

  const EyeBtn = ({ field }) => (
    <button type="button" className="pf-eye"
            onClick={() => setShow(s => ({ ...s, [field]: !s[field] }))}
            aria-label={show[field] ? 'Ocultar senha' : 'Mostrar senha'}>
      {show[field]
        ? <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5"><path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/><line x1="1" y1="1" x2="23" y2="23"/></svg>
        : <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>
      }
    </button>
  );

  const clrErr = (k) => { if (errors[k]) setErrors(e => ({ ...e, [k]: '' })); };

  const save = async () => {
    const e = {};
    if (!form.current) e.current = 'Informe sua senha atual';
    if (str.common) e.next = 'Senha comum ou com seu nome — escolha outra';
    else if (str.score < 5) e.next = 'Use 8+ caracteres com maiúscula, minúscula, número e símbolo';
    if (!form.confirm) e.confirm = 'Confirme a nova senha';
    else if (form.next !== form.confirm) e.confirm = 'As senhas não coincidem';
    setErrors(e);
    if (Object.keys(e).length) return;
    setSaving(true);
    try {
      const { error } = await window.HOROS_SB.auth.updateUser({ password: form.next });
      if (error) throw error;
      setForm({ current:'', next:'', confirm:'' });
      toast('Senha alterada com sucesso');
    } catch (err) { toast('Ocorreu um erro ao alterar a senha. Tente novamente ou fale com o suporte.', 'error'); }
    setSaving(false);
  };

  const CRITERIA = [
    { key:'length', label:'8+ chars' },
    { key:'upper', label:'Maiúscula' },
    { key:'lower', label:'Minúscula' },
    { key:'number', label:'Número' },
    { key:'symbol', label:'Símbolo' },
  ];

  return (
    <PCard title="Segurança" icon="lock">
      <div className="pf-sec-meta">
        <span>Última alteração de senha</span>
        <span>—</span>
      </div>
      <hr className="pf-divider" />

      <PField label="Senha atual" error={errors.current}>
        <div className="pf-pass-wrap">
          <input className="pf-input" type={show.current ? 'text' : 'password'}
                 value={form.current} placeholder="••••••••" aria-label="Senha atual"
                 onChange={e => { setForm(f => ({ ...f, current: e.target.value })); clrErr('current'); }} />
          <EyeBtn field="current" />
        </div>
      </PField>

      <PField label="Nova senha" error={errors.next}>
        <div className="pf-pass-wrap">
          <input className="pf-input" type={show.next ? 'text' : 'password'}
                 value={form.next} placeholder="••••••••" aria-label="Nova senha"
                 onChange={e => { setForm(f => ({ ...f, next: e.target.value })); clrErr('next'); }} />
          <EyeBtn field="next" />
        </div>
        {form.next && (
          <>
            <div className="pf-str-bar" role="progressbar" aria-valuenow={str.score} aria-valuemin={0} aria-valuemax={5} aria-label={`Força da senha: ${strLabel}`}>
              <div className="pf-str-fill" style={{ width:`${str.score * 20}%`, background: strColor }} />
            </div>
            <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', marginTop:6 }}>
              <div className="pf-criteria">
                {CRITERIA.map(c => (
                  <span key={c.key} className={`pf-criterion${str.checks[c.key] ? ' ok' : ''}`}>
                    {str.checks[c.key]
                      ? <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3"><path d="M20 6L9 17l-5-5"/></svg>
                      : <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><circle cx="12" cy="12" r="9"/></svg>
                    }
                    {c.label}
                  </span>
                ))}
              </div>
              {strLabel && <span style={{ fontSize:11, color: strColor, fontWeight:600 }}>{strLabel}</span>}
            </div>
          </>
        )}
      </PField>

      <PField label="Confirmar senha" error={errors.confirm}>
        <div className="pf-pass-wrap">
          <input className="pf-input" type={show.confirm ? 'text' : 'password'}
                 value={form.confirm} placeholder="••••••••" aria-label="Confirmar nova senha"
                 onChange={e => { setForm(f => ({ ...f, confirm: e.target.value })); clrErr('confirm'); }}
                 onKeyDown={e => e.key === 'Enter' && save()} />
          <EyeBtn field="confirm" />
        </div>
      </PField>

      <button className="btn-primary" style={{ width:'100%' }} onClick={save}
              disabled={saving || !form.current || !form.next || !form.confirm} aria-busy={saving}>
        {saving ? <><span className="pf-spin-sm" /> Alterando…</> : 'Alterar senha'}
      </button>
    </PCard>
  );
}

// ─── SESSÕES ──────────────────────────────────────────────────────────────────
function SessoesCard({ toast }) {
  const [endingAll, setEndingAll] = React.useState(false);
  const ua = navigator.userAgent;
  const browser = ua.includes('Edg') ? 'Edge' : ua.includes('Chrome') ? 'Chrome' : ua.includes('Firefox') ? 'Firefox' : ua.includes('Safari') ? 'Safari' : 'Navegador';
  const os = ua.includes('Windows') ? 'Windows' : ua.includes('Mac') ? 'macOS' : ua.includes('Linux') ? 'Linux' : ua.includes('Android') ? 'Android' : ua.includes('iPhone') ? 'iOS' : 'Desconhecido';

  const endAll = async () => {
    setEndingAll(true);
    try {
      await window.HOROS_SB.auth.signOut({ scope: 'global' });
      window.location.href = 'login.html';
    } catch {
      toast('Erro ao encerrar sessões', 'error');
      setEndingAll(false);
    }
  };

  return (
    <PCard title="Sessões Ativas" icon="lock"
      action={
        <button className="pf-danger-btn" style={{ fontSize:11.5, padding:'5px 10px' }}
                onClick={endAll} disabled={endingAll}>
          {endingAll ? 'Encerrando…' : 'Encerrar todas'}
        </button>
      }
    >
      <div className="pf-session" role="listitem">
        <div className="pf-session-ico" aria-hidden="true">
          <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5"><rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8M12 17v4"/></svg>
        </div>
        <div className="pf-session-body">
          <div className="pf-session-title">
            {browser} · {os}
            <span className="pf-badge-current">Sessão atual</span>
          </div>
          <div className="pf-session-sub">Ativo agora · Este dispositivo</div>
        </div>
      </div>
    </PCard>
  );
}

// ─── 2FA ──────────────────────────────────────────────────────────────────────
function DoisFatoresCard({ toast }) {
  const [phase, setPhase]           = React.useState('loading'); // loading|idle|setup|active|disabling
  const [factorId, setFactorId]     = React.useState(null);
  const [enrolledAt, setEnrolledAt] = React.useState(null);
  const [qrCode, setQrCode]         = React.useState('');
  const [secret, setSecret]         = React.useState('');
  const [code, setCode]             = React.useState('');
  const [codeErr, setCodeErr]       = React.useState('');
  const [disableCode, setDisableCode] = React.useState('');
  const [disableErr, setDisableErr]   = React.useState('');
  const [showSecret, setShowSecret] = React.useState(false);
  const [busy, setBusy]             = React.useState(false);

  // ── Check existing factors on mount ──────────────────────────────────────
  React.useEffect(() => {
    const load = async () => {
      try {
        const sb = window.HOROS_SB;
        if (!sb) { setTimeout(load, 400); return; }
        const { data, error } = await sb.auth.mfa.listFactors();
        if (error) throw error;
        const verified   = (data?.totp || []).find(f => f.status === 'verified');
        const unverified = (data?.totp || []).find(f => f.status === 'unverified');
        if (verified) {
          setFactorId(verified.id);
          setEnrolledAt(verified.created_at);
          setPhase('active');
        } else {
          if (unverified) {
            await sb.auth.mfa.unenroll({ factorId: unverified.id }).catch(() => {});
          }
          setPhase('idle');
        }
      } catch (e) {
        console.error('[2fa]', e);
        setPhase('idle');
      }
    };
    load();
  }, []);

  // ── Start enrollment ──────────────────────────────────────────────────────
  const startEnroll = async () => {
    setBusy(true);
    try {
      const userName = window.HOROS_USER?.email || 'usuario';
      const { data, error } = await window.HOROS_SB.auth.mfa.enroll({
        factorType: 'totp',
        issuer: 'Horos',
        friendlyName: userName,
      });
      if (error) throw error;
      setFactorId(data.id);
      setQrCode(data.totp.qr_code);
      setSecret(data.totp.secret);
      setCode('');
      setCodeErr('');
      setShowSecret(false);
      setPhase('setup');
    } catch (e) {
      toast('Ocorreu um erro ao iniciar o 2FA. Tente novamente ou fale com o suporte.', 'error');
    }
    setBusy(false);
  };

  // ── Verify TOTP code ──────────────────────────────────────────────────────
  const verify = async () => {
    if (code.length !== 6 || busy) return;
    setBusy(true);
    setCodeErr('');
    try {
      const { error } = await window.HOROS_SB.auth.mfa.challengeAndVerify({ factorId, code });
      if (error) throw error;
      setEnrolledAt(new Date().toISOString());
      setPhase('active');
      toast('2FA ativado com sucesso');
    } catch (e) {
      const msg = e.message || '';
      setCodeErr(msg.toLowerCase().includes('invalid') || msg.toLowerCase().includes('expired')
        ? 'Código inválido ou expirado — tente novamente'
        : 'Código incorreto — tente novamente');
    }
    setBusy(false);
  };

  // ── Cancel enrollment (unenroll the pending factor) ───────────────────────
  const cancelSetup = async () => {
    if (factorId) {
      await window.HOROS_SB.auth.mfa.unenroll({ factorId }).catch(() => {});
    }
    setFactorId(null);
    setQrCode('');
    setSecret('');
    setCode('');
    setCodeErr('');
    setPhase('idle');
  };

  // ── Disable 2FA — step 1: ask for current TOTP code ─────────────────────
  const startDisable = () => {
    setDisableCode('');
    setDisableErr('');
    setPhase('disabling');
  };

  // ── Disable 2FA — step 2: verify AAL2 then unenroll ──────────────────────
  const confirmDisable = async () => {
    if (disableCode.length !== 6 || busy) return;
    setBusy(true);
    setDisableErr('');
    try {
      // Elevate session to AAL2 (required by Supabase before unenroll)
      const { error: verErr } = await window.HOROS_SB.auth.mfa.challengeAndVerify({
        factorId,
        code: disableCode,
      });
      if (verErr) throw verErr;

      const { error: unErr } = await window.HOROS_SB.auth.mfa.unenroll({ factorId });
      if (unErr) throw unErr;

      setFactorId(null);
      setEnrolledAt(null);
      setDisableCode('');
      setPhase('idle');
      toast('2FA desativado');
    } catch (e) {
      const msg = e.message || '';
      setDisableErr(
        msg.toLowerCase().includes('invalid') || msg.toLowerCase().includes('expired')
          ? 'Código inválido ou expirado — tente novamente'
          : 'Código incorreto — tente novamente'
      );
    }
    setBusy(false);
  };

  const IcoLock = () => (
    <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" aria-hidden="true">
      <rect x="5" y="11" width="14" height="10" rx="2"/><path d="M8 11V7a4 4 0 1 1 8 0v4"/>
    </svg>
  );
  const IcoCheck = () => (
    <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" aria-hidden="true">
      <path d="M20 6L9 17l-5-5"/>
    </svg>
  );

  // ── LOADING ───────────────────────────────────────────────────────────────
  if (phase === 'loading') return (
    <PCard title="Autenticação em 2 Fatores" icon="lock">
      <div style={{ display:'flex', alignItems:'center', gap:9, color:'var(--fg-4)', fontSize:13 }}>
        <div className="pf-spinner" style={{ width:16, height:16 }} aria-hidden="true" />
        Verificando status…
      </div>
    </PCard>
  );

  // ── IDLE ──────────────────────────────────────────────────────────────────
  if (phase === 'idle') return (
    <PCard title="Autenticação em 2 Fatores" icon="lock">
      <div className="pf-2fa-status pf-2fa-off" role="status">
        <IcoLock />
        Desativado — sua conta usa apenas senha
      </div>
      <p className="pf-2fa-desc">
        O 2FA adiciona uma segunda camada de proteção. Além da senha, você precisará de um código gerado pelo seu app autenticador a cada login.
      </p>
      <button className="btn-primary" style={{ width:'100%' }} onClick={startEnroll}
              disabled={busy} aria-busy={busy}>
        {busy
          ? <><span className="pf-spin-sm" aria-hidden="true" /> Preparando…</>
          : 'Ativar 2FA'
        }
      </button>
    </PCard>
  );

  // ── SETUP ─────────────────────────────────────────────────────────────────
  if (phase === 'setup') return (
    <PCard title="Autenticação em 2 Fatores" icon="lock">
      <p className="pf-2fa-desc">
        Abra o <strong>Google Authenticator</strong>, <strong>Authy</strong> ou qualquer app TOTP e escaneie o QR Code abaixo.
      </p>

      {qrCode && (
        <div className="pf-qr-wrap" aria-label="QR Code de configuração 2FA">
          <div className="pf-qr-inner">
            <img src={qrCode} alt="QR Code para configurar 2FA" width={200} height={200}
                 style={{ display:'block', imageRendering:'pixelated' }} />
          </div>
        </div>
      )}

      <div className="pf-secret-row">
        <span className="pf-2fa-desc" style={{ margin:0 }}>Não consegue escanear?</span>
        <button className="pf-ghost-btn pf-ghost-btn--sm"
                onClick={() => setShowSecret(s => !s)}
                aria-expanded={showSecret}>
          {showSecret ? 'Ocultar chave' : 'Ver chave manual'}
        </button>
      </div>

      {showSecret && (
        <div className="pf-secret-wrap" role="region" aria-label="Chave secreta manual">
          <code className="pf-secret-code">{secret}</code>
          <p className="pf-helper" style={{ margin:0 }}>
            Copie esta chave e insira manualmente no app autenticador.
          </p>
        </div>
      )}

      <PField label="Código de verificação (6 dígitos)" error={codeErr}>
        <input className="pf-input pf-otp" value={code} maxLength={6} inputMode="numeric"
               autoComplete="one-time-code" placeholder="000000" aria-label="Código TOTP"
               onChange={e => { setCode(e.target.value.replace(/\D/g,'').slice(0,6)); setCodeErr(''); }}
               onKeyDown={e => e.key === 'Enter' && verify()} />
      </PField>

      <div className="pf-btn-row">
        <button className="pf-ghost-btn" onClick={cancelSetup} disabled={busy}>Cancelar</button>
        <button className="btn-primary" style={{ flex:1 }}
                disabled={code.length !== 6 || busy} onClick={verify} aria-busy={busy}>
          {busy
            ? <><span className="pf-spin-sm" aria-hidden="true" /> Verificando…</>
            : 'Confirmar e ativar'
          }
        </button>
      </div>
    </PCard>
  );

  // ── DISABLING — confirm with current TOTP code ───────────────────────────
  if (phase === 'disabling') {
    const since2 = enrolledAt
      ? new Date(enrolledAt).toLocaleDateString('pt-BR', { day:'2-digit', month:'long', year:'numeric' })
      : '—';
    return (
      <PCard title="Autenticação em 2 Fatores" icon="lock">
        <div className="pf-2fa-status pf-2fa-on" role="status">
          <IcoCheck />
          Ativo · App autenticador (TOTP)
        </div>
        <div className="pf-2fa-meta">Ativado em {since2}</div>

        <div className="pf-2fa-disable-box">
          <p className="pf-2fa-desc" style={{ margin:0 }}>
            Para confirmar a desativação, insira o código atual do seu app autenticador.
          </p>
          <PField label="Código de verificação (6 dígitos)" error={disableErr}>
            <input className="pf-input pf-otp" value={disableCode} maxLength={6}
                   inputMode="numeric" autoComplete="one-time-code" placeholder="000000"
                   aria-label="Código TOTP para desativar"
                   onChange={e => { setDisableCode(e.target.value.replace(/\D/g,'').slice(0,6)); setDisableErr(''); }}
                   onKeyDown={e => e.key === 'Enter' && confirmDisable()} />
          </PField>
          <div className="pf-btn-row">
            <button className="pf-ghost-btn" onClick={() => setPhase('active')} disabled={busy}>
              Cancelar
            </button>
            <button className="pf-danger-btn pf-danger-btn--armed" style={{ flex:1 }}
                    disabled={disableCode.length !== 6 || busy} onClick={confirmDisable} aria-busy={busy}>
              {busy
                ? <><span className="pf-spin-sm" style={{ borderTopColor:'var(--danger)' }} aria-hidden="true" /> Desativando…</>
                : 'Confirmar desativação'
              }
            </button>
          </div>
        </div>
      </PCard>
    );
  }

  // ── ACTIVE ────────────────────────────────────────────────────────────────
  const since = enrolledAt
    ? new Date(enrolledAt).toLocaleDateString('pt-BR', { day:'2-digit', month:'long', year:'numeric' })
    : '—';

  return (
    <PCard title="Autenticação em 2 Fatores" icon="lock">
      <div className="pf-2fa-status pf-2fa-on" role="status">
        <IcoCheck />
        Ativo · App autenticador (TOTP)
      </div>
      <div className="pf-2fa-meta">Ativado em {since}</div>
      <p className="pf-2fa-desc" style={{ marginTop:8 }}>
        A cada novo login você precisará informar o código gerado pelo seu app autenticador após a senha.
      </p>
      <button className="pf-danger-btn" style={{ width:'100%' }}
              onClick={startDisable} disabled={busy} aria-busy={busy}>
        {busy
          ? <><span className="pf-spin-sm" style={{ borderTopColor:'var(--danger)' }} aria-hidden="true" /> Desativando…</>
          : 'Desativar 2FA'
        }
      </button>
    </PCard>
  );
}

// ─── PREFERÊNCIAS ─────────────────────────────────────────────────────────────
function PreferenciasCard({ theme, onThemeChange, toast }) {
  const loadPref = (k, def) => { try { return JSON.parse(localStorage.getItem('horos-prefs')||'{}')[k]??def; } catch { return def; } };
  const savePref = (k, v) => { try { const p=JSON.parse(localStorage.getItem('horos-prefs')||'{}'); p[k]=v; localStorage.setItem('horos-prefs',JSON.stringify(p)); } catch {} };

  const [density, setDensity] = React.useState(() => loadPref('density','comfortable'));
  const [dateFmt, setDateFmt] = React.useState(() => loadPref('dateFormat','dd/mm/yyyy'));
  const [anim, setAnim] = React.useState(() => loadPref('animations', true));
  const [themeMode, setThemeMode] = React.useState(() => (window.horosThemeMode ? window.horosThemeMode() : 'system'));

  const setPref = (setter, k, v) => { setter(v); savePref(k, v); toast('Preferência salva'); };

  return (
    <PCard title="Preferências" icon="settings">
      <PField label="Tema">
        <OptionGroup
          value={themeMode}
          options={[{ id:'system', label:'Seguir o sistema' }, { id:'light', label:'Claro' }, { id:'dark', label:'Escuro' }]}
          onChange={v => {
            setThemeMode(v);
            const t = window.horosSetThemeMode ? window.horosSetThemeMode(v) : v;
            if (t === 'light' || t === 'dark') onThemeChange(t);
            toast('Tema alterado');
          }}
        />
      </PField>

      <PField label="Densidade da interface">
        <OptionGroup
          value={density}
          options={[{ id:'compact', label:'Compacta' }, { id:'comfortable', label:'Confortável' }, { id:'spacious', label:'Espaçosa' }]}
          onChange={v => setPref(setDensity, 'density', v)}
        />
      </PField>

      <PField label="Formato de data">
        <OptionGroup
          value={dateFmt}
          options={[{ id:'dd/mm/yyyy', label:'DD/MM/AAAA' }, { id:'mm/dd/yyyy', label:'MM/DD/AAAA' }, { id:'yyyy-mm-dd', label:'AAAA-MM-DD' }]}
          onChange={v => setPref(setDateFmt, 'dateFormat', v)}
        />
      </PField>

      <div className="pf-sw-row">
        <div>
          <div className="pf-sw-label">Animações</div>
          <div className="pf-sw-sub">Microanimações e transições na interface</div>
        </div>
        <PSwitch checked={anim} onChange={v => setPref(setAnim, 'animations', v)} />
      </div>

      <div className="pf-sw-row">
        <div>
          <div className="pf-sw-label">Idioma</div>
          <div className="pf-sw-sub">Português (Brasil)</div>
        </div>
        <span className="pf-lang-badge">PT-BR</span>
      </div>
    </PCard>
  );
}

// ─── MINHA IA ─────────────────────────────────────────────────────────────────
// Cada pessoa tem a própria IA: profiles.ai_settings (get_my_ai/set_my_ai) + o que ela aprendeu (ai_user_memories).
// Vale para todas as IAs do Horos, com ou sem copiloto. Campo vazio usa o texto antigo da empresa.
const AI_FIELDS = [
  { k: 'about',         label: 'Sobre você e a empresa',    get ph() { return segTerm('product') === 'imóvel' ? 'Ex.: Imobiliária em BH desde 2010, especializada em lançamentos na zona norte.' : 'Ex.: Empresa em BH desde 2010, atendemos toda a região metropolitana.'; }, rows: 2 },
  { k: 'offer',         label: 'O que você vende',         ph: 'Produtos, serviços, faixas de preço que podem ser citadas…', rows: 3 },
  { k: 'differentials', label: 'Diferenciais',               ph: 'Por que escolher você e não o concorrente?', rows: 2 },
  { k: 'audience',      label: 'Público',                    ph: 'Quem costuma comprar de você?', rows: 2 },
  { k: 'tone',          label: 'Tom de voz',                 ph: 'Ex.: próximo e leve, sem gírias, sempre chamar pelo primeiro nome.', rows: 2 },
  { k: 'faq',           label: 'Perguntas frequentes',       ph: 'Uma por linha, com a resposta. Ex.: Aceitam Pix? Sim, à vista com 5% de desconto.', rows: 4 },
  { k: 'forbidden',     label: 'A IA nunca deve',            ph: 'Ex.: falar de concorrentes, prometer desconto, passar valor sem simulação.', rows: 2 },
  { k: 'disclaimer',    label: 'Aviso obrigatório',          ph: 'Texto que precisa aparecer nas respostas, se houver.', rows: 2 },
];

const AI_MEM_KIND = { preference: 'Preferência', fact: 'Como você trabalha', correction: 'Correção' };

function IACard({ toast }) {
  const [state, setState] = React.useState(null); // { settings, company, style_pairs }
  const [form, setForm] = React.useState({});
  const [learn, setLearn] = React.useState(true);
  const [mems, setMems] = React.useState([]);
  const [draft, setDraft] = React.useState('');
  const [busy, setBusy] = React.useState(false);
  const [err, setErr] = React.useState('');

  React.useEffect(() => {
    let alive = true;
    (async () => {
      const sb = window.HOROS_SB;
      if (!sb) return;
      const [{ data, error }, m] = await Promise.all([
        sb.rpc('get_my_ai'),
        sb.from('ai_user_memories').select('id,kind,content,source').order('updated_at', { ascending: false }),
      ]);
      if (!alive) return;
      if (error || !data) { setErr('Não foi possível carregar sua IA.'); setState({ settings: {}, company: {}, style_pairs: 0 }); return; }
      setState(data); setForm(data.settings || {}); setLearn(data.settings?.learn !== false);
      setMems(m.data || []);
    })();
    return () => { alive = false; };
  }, []);

  if (!state) return (
    <PCard title="Minha IA" icon="ai" span2>
      <SkelText lines={4} last="60%" />
    </PCard>
  );

  const norm = (o) => JSON.stringify(Object.fromEntries(AI_FIELDS.map(f => [f.k, (o?.[f.k] || '').trim()])));
  const dirty = norm(form) !== norm(state.settings);

  const persist = async (f, l) => {
    const settings = { ...Object.fromEntries(AI_FIELDS.map(x => [x.k, (f[x.k] || '').trim()]).filter(([, v]) => v)), learn: l };
    const { data, error } = await window.HOROS_SB.rpc('set_my_ai', { p_settings: settings });
    if (error || !data) { setErr('Não foi possível salvar. Tente de novo.'); return false; }
    setState(s => ({ ...s, settings }));
    return true;
  };
  const save = async () => {
    setBusy(true); setErr('');
    const ok = await persist(form, learn);
    setBusy(false);
    if (ok) toast('Sua IA foi atualizada');
  };
  const toggleLearn = async () => {
    const next = !learn;
    setLearn(next); setErr('');
    if (!await persist(state.settings, next)) setLearn(!next);
  };
  const addMem = async (e) => {
    e.preventDefault();
    const content = draft.trim();
    if (content.length < 3) return;
    const { data, error } = await window.HOROS_SB.from('ai_user_memories')
      .insert({ user_id: window.HOROS_USER?.id, kind: 'fact', content: content.slice(0, 400), source: 'manual' })
      .select('id,kind,content,source').single();
    if (error) { setErr('Não foi possível guardar. Tente de novo.'); return; }
    setMems(m => [data, ...m]); setDraft('');
  };
  const delMem = async (id) => {
    const prev = mems;
    setMems(m => m.filter(x => x.id !== id));
    const { error } = await window.HOROS_SB.from('ai_user_memories').delete().eq('id', id);
    if (error) { setMems(prev); setErr('Não foi possível apagar. Tente de novo.'); }
  };

  return (
    <>
      <PCard title="O que a IA aprendeu com você" icon="sparkles" span2>
        <p className="pf-ai-lead">
          A IA guarda suas preferências e correções a cada conversa e lê como você escreve no WhatsApp. Funciona com o copiloto ligado ou desligado. Só você vê e altera o que está aqui.
        </p>
        <div className="pf-sw-row">
          <label htmlFor="ai-learn" style={{ flex: 1, cursor: 'pointer' }}>
            <div className="pf-sw-label">Aprender com minhas conversas no chat de IA</div>
            <div className="pf-sw-sub">{state.style_pairs > 0 ? `Seu jeito de escrever: ${Number(state.style_pairs).toLocaleString('pt-BR')} respostas analisadas no WhatsApp.` : 'Seu jeito de escrever aparece aqui depois das suas primeiras respostas no WhatsApp.'}</div>
          </label>
          <PSwitch id="ai-learn" checked={learn} onChange={toggleLearn} />
        </div>
        <form className="pf-ai-memadd" onSubmit={addMem}>
          <input value={draft} maxLength={400} onChange={e => setDraft(e.target.value)} placeholder="Ensine algo. Ex.: atendo só a zona norte de BH e prefiro respostas curtas." aria-label="Ensinar algo à IA" />
          <button className="btn-primary" type="submit" disabled={draft.trim().length < 3}>Guardar</button>
        </form>
        {mems.length === 0
          ? <p className="pf-ai-note" style={{ marginTop: 12 }}>Nada guardado ainda. Converse com a IA ou ensine algo acima.</p>
          : <ul className="pf-ai-mems">
              {mems.map(m => (
                <li key={m.id}>
                  <span className="pf-ai-memtxt"><b>{AI_MEM_KIND[m.kind] || 'Nota'}</b>{m.content}</span>
                  <button type="button" className="pf-ai-memdel" title="Apagar" aria-label="Apagar" onClick={() => delMem(m.id)}><Icon name="trash" size={15} /></button>
                </li>
              ))}
            </ul>}
      </PCard>
      <PCard title="Instruções para a sua IA" icon="ai" span2>
        <p className="pf-ai-lead">
          Opcional. A IA trata como verdade o que você escrever aqui. Campo vazio usa o texto da empresa, quando houver.
        </p>
        <div className="pf-ai-fields">
          {AI_FIELDS.map(f => (
            <PField key={f.k} label={f.label}>
              <PTextarea value={form[f.k] || ''} rows={f.rows} placeholder={state.company?.[f.k] ? `Da empresa: ${state.company[f.k]}` : f.ph} maxLen={2000}
                         onChange={v => setForm(p => ({ ...p, [f.k]: v }))} />
            </PField>
          ))}
        </div>
        {err && <div className="pf-helper pf-err" role="alert" style={{ marginTop: 10 }}>{err}</div>}
        <div className="pf-ai-foot">
          <span className="pf-ai-note">{dirty ? 'Alterações não salvas' : 'Tudo salvo'}</span>
          <button className="btn-primary" onClick={save} disabled={!dirty || busy} aria-busy={busy}>
            {busy ? 'Salvando…' : 'Salvar'}
          </button>
        </div>
      </PCard>
    </>
  );
}

// ─── NOTIFICAÇÕES ─────────────────────────────────────────────────────────────
// Notificações no celular/computador (Web Push): o aparelho se inscreve por HorosNotif.enablePush()
// e o banco manda o push de toda notificação nova (trg_notification_push → /api/push-send).
// O que a pessoa desliga aqui fica em notification_preferences.push_modules (vale para todos os aparelhos).
const PUSH_MODULES = [
  { key:'leads',    label:'Leads', sub:'Lead novo, lead repassado para você, sugestão de resposta da IA' },
  { key:'messages', label:'Mensagens do WhatsApp', sub:'Cada conversa vira um aviso só (grupos ficam de fora)' },
  { key:'agenda',   label:'Agenda', sub:'Compromissos e visitas' },
  { key:'system',   label:'Conta e suporte', sub:'Assinatura, resposta do suporte, comissões e avisos do Horos' },
];
const PUSH_DEFAULTS = { messages:true, leads:true, agenda:true, system:true };

function NotificacoesCard() {
  const N = window.HorosNotif;
  const [state, setState] = React.useState(() => N ? N.pushState() : 'unsupported');
  const [busy, setBusy] = React.useState('');
  const [msg, setMsg] = React.useState('');
  const [mods, setMods] = React.useState(PUSH_DEFAULTS);

  React.useEffect(() => {
    const on = e => setState(e.detail);
    window.addEventListener('horos:push-state', on);
    const t = setTimeout(() => N && setState(N.pushState()), 1500);   // inscrição confirmada ao abrir a tela
    const uid = window.HOROS_USER?.id;
    if (uid) window.HOROS_SB.from('notification_preferences').select('push_modules').eq('user_id', uid).maybeSingle()
      .then(({ data }) => { if (data?.push_modules) setMods({ ...PUSH_DEFAULTS, ...data.push_modules }); }, () => {});
    return () => { window.removeEventListener('horos:push-state', on); clearTimeout(t); };
  }, []);

  const toggle = async k => {
    const next = { ...mods, [k]: !mods[k] };
    setMods(next);
    const { error } = await window.HOROS_SB.from('notification_preferences')
      .upsert({ user_id: window.HOROS_USER.id, push_modules: next, updated_at: new Date().toISOString() }, { onConflict: 'user_id' });
    if (error) { setMods(mods); setMsg('Não foi possível salvar. Tente de novo.'); }
  };

  const enable = async () => {
    setBusy('on'); setMsg('');
    const r = await N.enablePush();
    setState(r); setBusy('');
    if (r === 'on') { setMsg('Pronto! Mandamos um aviso de teste para este aparelho.'); N.sendTestPush().catch(() => {}); }
    else if (r === 'off') setMsg('A permissão não foi dada. Toque em Ativar de novo e escolha Permitir.');
  };
  const disable = async () => { setBusy('off'); setMsg(''); setState(await N.disablePush()); setBusy(''); };
  const test = async () => {
    setBusy('test'); setMsg('');
    const n = await N.sendTestPush().catch(() => 0);
    setBusy(''); setMsg(n ? 'Aviso de teste enviado. Deve chegar em alguns segundos.' : 'Nenhum aparelho recebeu. Desative e ative de novo.');
  };

  const STATUS = {
    on:  { t:'Ligadas neste aparelho', s:'Os avisos chegam mesmo com o Horos fechado.' },
    off: { t:'Desligadas neste aparelho', s:'Ative para receber os avisos de novos leads e mensagens na hora, mesmo com o Horos fechado.' },
    denied: { t:'Bloqueadas pelo navegador', s:'Toque no cadeado ao lado do endereço do site → Notificações → Permitir. Depois recarregue a página.' },
    'ios-install': { t:'No iPhone, instale o Horos primeiro', s:'No Safari, toque em Compartilhar e em "Adicionar à Tela de Início". Abra o Horos pelo ícone novo e volte aqui para ativar.' },
    unsupported: { t:'Este navegador não recebe notificações', s:'Use o Chrome no Android ou no computador, ou o Safari no iPhone (com o Horos na Tela de Início).' },
  }[state] || { t:'', s:'' };

  return (
    <PCard title="Notificações" icon="bell">
      <div className="pf-notif-group">Neste aparelho</div>
      <div className="pf-sw-row" style={{ alignItems:'flex-start', gap:16, flexWrap:'wrap' }}>
        <div style={{ flex:'1 1 240px' }}>
          <div className="pf-sw-label">{STATUS.t}</div>
          <div className="pf-sw-sub">{STATUS.s}</div>
          {msg && <div className="pf-sw-sub" style={{ color:'var(--accent)', marginTop:6 }} role="status">{msg}</div>}
        </div>
        <div style={{ display:'flex', gap:8, flexWrap:'wrap' }}>
          {state === 'on' && <button className="pf-ghost-btn" onClick={test} disabled={!!busy}>{busy === 'test' ? 'Enviando…' : 'Enviar teste'}</button>}
          {state === 'on' && <button className="pf-ghost-btn" onClick={disable} disabled={!!busy}>{busy === 'off' ? 'Desativando…' : 'Desativar'}</button>}
          {state === 'off' && <button className="btn-primary" onClick={enable} disabled={!!busy}>{busy === 'on' ? 'Ativando…' : 'Ativar notificações'}</button>}
        </div>
      </div>

      <div className="pf-notif-group">O que avisar</div>
      {PUSH_MODULES.map(it => (
        <div key={it.key} className="pf-sw-row" style={{ borderTop:'1px solid var(--line)' }}>
          <label htmlFor={`notif-${it.key}`} style={{ flex:1, cursor:'pointer' }}>
            <div className="pf-sw-label">{it.label}</div>
            <div className="pf-sw-sub">{it.sub}</div>
          </label>
          <PSwitch id={`notif-${it.key}`} checked={!!mods[it.key]} onChange={() => toggle(it.key)} />
        </div>
      ))}
    </PCard>
  );
}

// ─── ASSINATURA (Stripe — /api/billing) ───────────────────────────────────────
// Tudo dentro do Horos: resumo, cartão (Stripe Elements), upgrade (modal "Planos que crescem com você"),
// cancelamento em etapas e faturas.
// Preços e textos iguais a PLANS/TEAM de pricing.html e CATALOG de api/_lib/stripe.js (em reais).
const BILL_PRICES = {
  pro:  { monthly: 79, annually: 660 },
  max:  { monthly: 239, annually: 2388 },
  team_std:  { monthly: 69, annually: 588 },
  team_prem: { monthly: 219, annually: 2148 },
  ent_seat:  { annually: 1908 },
  // Plano Distribuição (por empresa) — migration 20261014_plan_distribution.sql
  dist_10:  { monthly: 149, annually: 1488 },
  dist_30:  { monthly: 249, annually: 2484 },
  dist_unl: { monthly: 399, annually: 3984 },
};
// Preços reais da tabela plan_prices (editados no portal admin) — atualiza BILL_PRICES no lugar
const _billPricesReady = (async () => {
  try {
    const sb = window.HOROS_SB || await new Promise(r => { const t = setInterval(() => { if (window.HOROS_SB) { clearInterval(t); r(window.HOROS_SB); } }, 100); setTimeout(() => { clearInterval(t); r(null); }, 8000); });
    if (!sb) return;
    const { data } = await sb.from('plan_prices').select('key,monthly,annually');
    (data || []).forEach(r => {
      const p = BILL_PRICES[r.key];
      if (!p) return;
      if (p.monthly != null && Number(r.monthly) > 0) p.monthly = Number(r.monthly);
      if (p.annually != null && Number(r.annually) > 0) p.annually = Number(r.annually);
    });
    window.dispatchEvent(new CustomEvent('horos:plan-prices'));
  } catch (e) {}
})();
function useBillPrices() {
  const [, bump] = React.useState(0);
  React.useEffect(() => {
    const on = () => bump(x => x + 1);
    window.addEventListener('horos:plan-prices', on);
    return () => window.removeEventListener('horos:plan-prices', on);
  }, []);
}
const BILL_PLAN = { pro: 'Pro', max: 'Max', team: 'Team', enterprise: 'Enterprise', free: 'Sem plano',
  dist_10: 'Distribuição até 10', dist_30: 'Distribuição até 30', dist_unl: 'Distribuição sem limite' };
const _BILL_DIST_FEATS = ['Leads do Meta Ads, Google Ads, portais, formulário do site e API', 'Fila da equipe: rodízio ou menor carga, com pausa', 'Regras por origem, região, valor e temperatura', 'Aviso na hora para quem recebe o lead', 'Tela Leads recebidos: origem, quem recebeu e situação', 'Passar o lead para outra pessoa', 'Zapier, Make e webhooks para o seu CRM'];
const BILL_PLANS_INFO = {
  pro: { tagline: 'O essencial para quem atende sozinho', from: 'Inclui:',
    features: ['CRM com Kanban e funil sugerido para o seu ramo', 'WhatsApp (1 número) com respostas rápidas e etiquetas', 'Horos 360 com a conversa do cliente', 'Leads do Meta Ads e do formulário do site', 'Importação de planilha com deduplicação', 'Ficha do cliente, documentos e autorização de crédito por link', 'Agenda com Google Agenda e confirmação pelo WhatsApp', 'Metas, funil da meta e Assistente do Dashboard', 'Chat com a IA do Horos', 'Disparos em massa com limite diário seguro', 'Copiloto para testar: 50 sugestões de resposta', 'Suporte por e-mail'] },
  max: { tagline: 'O Horos trabalhando por você', from: 'Tudo do Pro, mais:',
    features: ['Copiloto sem limite: sugestões de resposta no WhatsApp', 'Assistente automático que responde do seu jeito quando você demora', 'Automações de fluxo (mensagens, etapas, avisos e esperas)', 'Relatório com IA e relatórios rápidos', 'Suporte prioritário'] },
  team: { tagline: 'Para equipes de 3 a 150 pessoas', from: 'Tudo do Pro para cada pessoa, mais:',
    features: ['Workspace da empresa, equipes e cargos', 'Distribuição automática de leads (rodízio ou menor carga)', 'Regras de roteamento por origem, região e valor', 'Etapa de cada pessoa no rodízio e repasse de leads', 'Ranking, metas e relatórios por pessoa', 'Assistente vinculada a um vendedor (mesmo WhatsApp e funil)', 'Assentos Premium com tudo do Max: copiloto e automações', 'Logo da empresa no link de crédito', 'Implantação assistida'] },
  dist_10:  { tagline: 'Só receber e distribuir leads · até 10 pessoas', from: 'Inclui:', features: _BILL_DIST_FEATS },
  dist_30:  { tagline: 'Só receber e distribuir leads · até 30 pessoas', from: 'Tudo do Até 10, mais:', features: ['Até 30 pessoas na imobiliária', 'Várias equipes, cada uma com a sua fila'] },
  dist_unl: { tagline: 'Só receber e distribuir leads · sem limite', from: 'Tudo do Até 30, mais:', features: ['Pessoas ilimitadas', 'Ajuda para configurar as integrações'] },
  enterprise: { tagline: 'Para redes com 20 pessoas ou mais', from: 'Tudo do Team com assentos Premium, mais:',
    features: ['Gerente de conta exclusivo', 'Treinamento da equipe', 'Integrações sob medida (portais, CV CRM, sistemas)', 'Várias equipes e regionais', 'Contrato anual · Boleto ou cartão'] },
};
const BILL_STATUS = {
  trialing:   { t: 'Teste grátis' },
  active:     { t: 'Ativa' },
  past_due:   { t: 'Pagamento pendente', bad: true },
  unpaid:     { t: 'Não paga', bad: true },
  canceled:   { t: 'Cancelada' },
  incomplete: { t: 'Aguardando pagamento', bad: true },
  paused:     { t: 'Teste terminou', bad: true },
};
const BILL_INV = { paid: 'Paga', open: 'Em aberto', void: 'Anulada', uncollectible: 'Não cobrada' };
// 'paused' = o teste grátis (sem cartão) acabou sem forma de pagamento: acesso bloqueado até cadastrar o cartão
const BILL_LIVE = ['trialing', 'active', 'past_due', 'unpaid', 'incomplete', 'paused'];

// Desenhos de traço da tela de preços (pricing.html → ART)
const _BSV = { viewBox: '0 0 48 64', fill: 'none', stroke: 'currentColor', strokeWidth: 1.6, strokeLinecap: 'round', strokeLinejoin: 'round', 'aria-hidden': true };
const _BSV64 = { ..._BSV, viewBox: '0 0 64 64' };
const _BSOFT = { fill: 'currentColor', fillOpacity: 0.22 };
const BILL_ART = {
  pro: <svg {..._BSV}><circle cx="24" cy="11" r="5.5"/><circle cx="24" cy="11" r="1.8" fill="currentColor"/><path d="M24 16.5V60"/><path d="M24 38l-9-6M24 38l9-6"/><circle cx="12.5" cy="30.5" r="3"/><circle cx="35.5" cy="30.5" r="3"/></svg>,
  max: <svg {..._BSV}><circle cx="24" cy="11" r="5.5"/><circle cx="24" cy="11" r="1.8" fill="currentColor"/><path d="M24 16.5V60"/><path d="M24 33l-9-6M24 33l9-6M24 47l-11-6M24 47l11-6"/><circle cx="12.5" cy="25.5" r="3"/><circle cx="35.5" cy="25.5" r="3"/><circle cx="10.5" cy="39.5" r="3"/><circle cx="37.5" cy="39.5" r="3"/></svg>,
  team: <svg {..._BSV64}><path d="M9 22 13.5 11h37L55 22"/><path d="M21.5 11 19 22M32 11v11M42.5 11 45 22"/><path d="M9 22v3c0 3 2.4 5.2 5.4 5.2S20 28 20 25v-3m0 3c0 3 2.6 5.2 6 5.2S32 28 32 25v-3m0 3c0 3 2.6 5.2 6 5.2s6-2.2 6-5.2v-3m0 3c0 3 2.6 5.2 5.6 5.2S55 28 55 25v-3"/><path d="M13 30.5V55h38V30.5"/><path d="M35 55V40h10v15" {..._BSOFT}/><path d="M19 40h10v8H19zM24 40v8"/><path d="M8 55h48"/></svg>,
  dist: <svg {..._BSV64}><circle cx="12" cy="32" r="6" {..._BSOFT}/><path d="M18 32h8c4 0 6-2 8-6l2-4c2-4 4-6 8-6h4M26 32h22M26 32c4 0 6 2 8 6l2 4c2 4 4 6 8 6h4"/><path d="m48 12 4 4-4 4M48 28l4 4-4 4M48 44l4 4-4 4"/></svg>,
  enterprise: <svg {..._BSV64}><path d="M14 56V13l20-6v49"/><path d="M34 25h16v31z" {..._BSOFT}/><path d="M34 25h16v31"/><path d="M8 56h48"/><path d="M20 18h8M20 24h8M20 30h8M20 36h8M39 32h6M39 38h6M39 44h6"/><path d="M20 46h8v10h-8z"/></svg>,
};

// um desenho por tamanho do plano Distribuição (mesmos de pricing.html → ART)
BILL_ART.dist_10 = <svg {..._BSV64}><circle cx="12" cy="32" r="6" {..._BSOFT}/><path d="M18 32h8M26 32c6 0 8-16 20-16M26 32h20M26 32c6 0 8 16 20 16"/><circle cx="50" cy="16" r="4"/><circle cx="50" cy="32" r="4"/><circle cx="50" cy="48" r="4"/></svg>;
BILL_ART.dist_30 = <svg {..._BSV64}><circle cx="10" cy="32" r="5" {..._BSOFT}/><path d="M15 32h3c4 0 6-6 8-10M15 32h3c4 0 6 6 8 10"/><circle cx="30" cy="20" r="4" {..._BSOFT}/><circle cx="30" cy="44" r="4" {..._BSOFT}/><path d="M34 20c5 0 9-12 15-12M34 20c5 0 10-3 15-3M34 20c5 0 9 6 15 6M34 44c5 0 9-6 15-6M34 44c5 0 10 3 15 3M34 44c5 0 9 12 15 12"/><circle cx="52" cy="8" r="3"/><circle cx="52" cy="17" r="3"/><circle cx="52" cy="26" r="3"/><circle cx="52" cy="38" r="3"/><circle cx="52" cy="47" r="3"/><circle cx="52" cy="56" r="3"/></svg>;
BILL_ART.dist_unl = <svg {..._BSV64}><circle cx="32" cy="32" r="28" strokeDasharray="1 4"/><circle cx="32" cy="32" r="6" {..._BSOFT}/><path d="M40.5 32h10.3M38 38l7.3 7.3M32 40.5v10.3M26 38l-7.3 7.3M23.5 32H13.2M26 26l-7.3-7.3M32 23.5V13.2M38 26l7.3-7.3"/><circle cx="54" cy="32" r="2.6"/><circle cx="47.6" cy="47.6" r="2.6"/><circle cx="32" cy="54" r="2.6"/><circle cx="16.4" cy="47.6" r="2.6"/><circle cx="10" cy="32" r="2.6"/><circle cx="16.4" cy="16.4" r="2.6"/><circle cx="32" cy="10" r="2.6"/><circle cx="47.6" cy="16.4" r="2.6"/></svg>;

function _billDate(iso, short) {
  if (!iso) return '—';
  return new Date(iso).toLocaleDateString('pt-BR', short ? { day: '2-digit', month: '2-digit', year: 'numeric' } : { day: '2-digit', month: 'long', year: 'numeric' });
}
function _billMoney(v, cents) {
  const n = cents ? (v || 0) / 100 : (v || 0);
  return n.toLocaleString('pt-BR', { style: 'currency', currency: 'BRL' });
}
function _billInt(v) { return Math.round(v).toLocaleString('pt-BR'); }
function _billTotal(plan, cycle, seats) {
  if (plan === 'enterprise') return (seats?.std || 0) * BILL_PRICES.ent_seat.annually;
  if (plan === 'team') return (seats?.std || 0) * BILL_PRICES.team_std[cycle] + (seats?.prem || 0) * BILL_PRICES.team_prem[cycle];
  return BILL_PRICES[plan]?.[cycle] || 0;
}
function _billNorm(s) { return String(s || '').normalize('NFD').replace(/[̀-ͯ]/g, '').toLowerCase().replace(/\s+/g, ' ').trim(); }
const _BRAND = { visa: 'Visa', mastercard: 'Mastercard', amex: 'American Express', elo: 'Elo', hipercard: 'Hipercard', discover: 'Discover', diners: 'Diners', link: 'Link' };

async function _billApi(method, body, query) {
  const tok = await window.horosToken();
  const r = await fetch('/api/billing' + (query || ''), {
    method, cache: 'no-store',
    headers: { Authorization: 'Bearer ' + tok, ...(body ? { 'Content-Type': 'application/json' } : {}) },
    body: body ? JSON.stringify(body) : undefined,
  });
  const j = await r.json().catch(() => ({}));
  if (!r.ok) throw new Error(j.message || 'Não foi possível concluir. Tente de novo.');
  return j;
}

function _loadStripeJs() {
  if (window.Stripe) return Promise.resolve(window.Stripe);
  if (!_loadStripeJs.p) _loadStripeJs.p = new Promise((ok, no) => {
    const s = document.createElement('script');
    s.src = 'https://js.stripe.com/v3/';
    s.onload = () => ok(window.Stripe);
    s.onerror = () => { _loadStripeJs.p = null; no(new Error('Não foi possível carregar o formulário do Stripe.')); };
    document.head.appendChild(s);
  });
  return _loadStripeJs.p;
}

// Modal genérico (portal no body, Esc e clique fora fecham quando não está ocupado)
function BillModal({ children, onClose, busy, wide, xl, label, title }) {
  const busyRef = React.useRef(busy);
  busyRef.current = busy;
  React.useEffect(() => {
    const onKey = e => { if (e.key === 'Escape' && !busyRef.current) onClose(); };
    window.addEventListener('keydown', onKey);
    const prev = document.body.style.overflow;
    document.body.style.overflow = 'hidden';
    return () => { window.removeEventListener('keydown', onKey); document.body.style.overflow = prev; };
  }, []);
  return ReactDOM.createPortal(
    <div className="pf-bill-ov" onMouseDown={e => e.target === e.currentTarget && !busyRef.current && onClose()}>
      <div className={`pf-bill-modal${wide ? ' wide' : ''}${xl ? ' xl' : ''}`} role="dialog" aria-modal="true" aria-label={label}>
        {title !== undefined && (
          <div className={`pf-bill-modal-head${xl ? ' bar' : ''}`}>
            <h3>{title}</h3>
            <button className="pf-bill-x" onClick={onClose} disabled={busy} aria-label="Fechar"><Icon name="close" size={16} /></button>
          </div>
        )}
        {children}
      </div>
    </div>,
    document.body
  );
}

// ── Forma de pagamento: nome, país, endereço (Address Element), cartão/Link (Payment Element),
//    nome diferente nas faturas e CPF/CNPJ. O número do cartão nunca passa pelo nosso servidor.
function _maskTax(type, v) {
  const d = String(v || '').replace(/\D/g, '').slice(0, type === 'br_cnpj' ? 14 : 11);
  if (type === 'br_cnpj') return d.replace(/^(\d{2})(\d)/, '$1.$2').replace(/^(\d{2})\.(\d{3})(\d)/, '$1.$2.$3').replace(/\.(\d{3})(\d)/, '.$1/$2').replace(/(\d{4})(\d)/, '$1-$2');
  return d.replace(/(\d{3})(\d)/, '$1.$2').replace(/(\d{3})(\d)/, '$1.$2').replace(/(\d{3})(\d{1,2})$/, '$1-$2');
}
function _taxOk(type, v) {
  const d = String(v || '').replace(/\D/g, '');
  if (!d) return true;
  if (type === 'br_cpf') {
    if (d.length !== 11 || /^(\d)\1+$/.test(d)) return false;
    const c = n => { let s = 0; for (let i = 0; i < n; i++) s += +d[i] * (n + 1 - i); const r = (s * 10) % 11; return r === 10 ? 0 : r; };
    return c(9) === +d[9] && c(10) === +d[10];
  }
  if (d.length !== 14 || /^(\d)\1+$/.test(d)) return false;
  const c = n => { const w = n === 12 ? [5,4,3,2,9,8,7,6,5,4,3,2] : [6,5,4,3,2,9,8,7,6,5,4,3,2]; const s = w.reduce((a, x, i) => a + x * +d[i], 0); const r = s % 11; return r < 2 ? 0 : 11 - r; };
  return c(12) === +d[12] && c(13) === +d[13];
}

function BillCardModal({ billing, onClose, onSaved }) {
  const addrRef = React.useRef(null);
  const payRef = React.useRef(null);
  const ref = React.useRef({});
  const [ready, setReady] = React.useState(false);
  const [busy, setBusy] = React.useState(false);
  const [err, setErr] = React.useState('');
  const b = billing || {};
  const [diffName, setDiffName] = React.useState(!!(b.invoice_name && b.name && b.invoice_name !== b.name));
  const [invName, setInvName] = React.useState(b.invoice_name && b.invoice_name !== b.name ? b.invoice_name : '');
  const [taxType, setTaxType] = React.useState(b.tax_id?.type || '');
  const [taxVal, setTaxVal] = React.useState(b.tax_id ? _maskTax(b.tax_id.type, b.tax_id.value) : '');
  const taxBad = !!taxType && !!taxVal && !_taxOk(taxType, taxVal);

  React.useEffect(() => {
    let alive = true;
    (async () => {
      try {
        const [{ client_secret, publishable_key }, StripeJs] = await Promise.all([_billApi('POST', { action: 'setup_intent' }), _loadStripeJs()]);
        if (!alive) return;
        const css = getComputedStyle(document.documentElement);
        const v = n => css.getPropertyValue(n).trim() || undefined;
        const dark = document.documentElement.getAttribute('data-theme') === 'dark';
        const stripe = StripeJs(publishable_key, { locale: 'pt-BR' });
        const elements = stripe.elements({
          clientSecret: client_secret,
          appearance: {
            theme: dark ? 'night' : 'stripe',
            labels: 'above',
            variables: { colorPrimary: v('--accent') || '#1F93FF', borderRadius: '8px', fontFamily: 'Roboto, Inter, system-ui, sans-serif',
              colorBackground: v('--bg-2') || v('--card'), colorText: v('--fg'), fontSizeBase: '14px', spacingUnit: '4px' },
            rules: { '.Label': { fontWeight: '600', marginBottom: '6px' } },
          },
        });
        const a = b.address || {};
        const addr = elements.create('address', {
          mode: 'billing',
          defaultValues: { name: b.name || '', address: { line1: a.line1 || '', line2: a.line2 || '', city: a.city || '', state: a.state || '', postal_code: a.postal_code || '', country: a.country || 'BR' } },
        });
        const pe = elements.create('payment', { layout: 'tabs', fields: { billingDetails: { name: 'never', address: 'never' } } });
        let n = 0;
        const done = () => { if (++n === 2 && alive) setReady(true); };
        addr.on('ready', done); pe.on('ready', done);
        addr.mount(addrRef.current); pe.mount(payRef.current);
        ref.current = { stripe, elements, addr };
      } catch (e) { alive && setErr(e.message); }
    })();
    return () => { alive = false; };
  }, []);

  const save = async () => {
    const { stripe, elements, addr } = ref.current;
    if (!stripe) return;
    if (taxBad) { setErr(taxType === 'br_cnpj' ? 'CNPJ inválido.' : 'CPF inválido.'); return; }
    if (diffName && !invName.trim()) { setErr('Informe o nome que deve aparecer nas faturas.'); return; }
    setBusy(true); setErr('');
    const { complete, value } = await addr.getValue();
    if (!complete) { setErr('Preencha o nome e o endereço.'); setBusy(false); return; }
    const { error, setupIntent } = await stripe.confirmSetup({
      elements, redirect: 'if_required',
      confirmParams: {
        return_url: window.location.origin + '/profile.html?aba=assinatura',
        payment_method_data: { billing_details: { name: value.name, address: value.address } },
      },
    });
    if (error) { setErr(error.message || 'Cartão não aceito.'); setBusy(false); return; }
    try {
      const r = await _billApi('POST', {
        action: 'set_payment_method', payment_method: setupIntent.payment_method,
        billing: {
          name: value.name, address: value.address,
          invoice_name: diffName ? invName.trim() : value.name,
          tax_id: taxType && taxVal ? { type: taxType, value: taxVal } : null,
        },
      });
      onSaved(r.payment_method, r);
    } catch (e) { setErr(e.message); setBusy(false); }
  };

  return (
    <BillModal onClose={onClose} busy={busy} wide label="Forma de pagamento" title="Forma de pagamento">
      {!ready && !err && <div className="pf-bill-skel" role="status" aria-label="Carregando formulário seguro"><span /><span /><span /><span /></div>}
      <div className={`pf-bill-stripe${ready ? '' : ' loading'}`}>
        <div ref={addrRef} />
        <div ref={payRef} style={{ marginTop: 16 }} />
      </div>

      {ready && (
        <>
          <label className="pf-bill-check">
            <input type="checkbox" checked={diffName} onChange={e => setDiffName(e.target.checked)} />
            <span>Usar um nome diferente nas faturas</span>
          </label>
          {diffName && (
            <div className="pf-bill-field">
              <label htmlFor="pf-bill-invname">Nome nas faturas</label>
              <input id="pf-bill-invname" className="pf-input" value={invName} onChange={e => setInvName(e.target.value)} placeholder={`Ex.: ${segTerm('Company', 'Imobiliária')} Exemplo Ltda`} maxLength={200} />
            </div>
          )}

          <div className="pf-bill-field">
            <label htmlFor="pf-bill-taxtype">ID fiscal (opcional)</label>
            <div className="pf-sw-sub" style={{ marginBottom: 8 }}>Ao informar um CNPJ, o nome nas faturas deve ser o da sua empresa.</div>
            <div className="pf-bill-tax">
              <select id="pf-bill-taxtype" className="pf-input" value={taxType} onChange={e => { setTaxType(e.target.value); setTaxVal(v => _maskTax(e.target.value, v)); }}>
                <option value="">Selecione o tipo</option>
                <option value="br_cpf">CPF</option>
                <option value="br_cnpj">CNPJ</option>
              </select>
              {taxType && (
                <input className="pf-input" inputMode="numeric" aria-label={taxType === 'br_cnpj' ? 'CNPJ' : 'CPF'} aria-invalid={taxBad}
                       placeholder={taxType === 'br_cnpj' ? '00.000.000/0000-00' : '000.000.000-00'}
                       value={taxVal} onChange={e => setTaxVal(_maskTax(taxType, e.target.value))} />
              )}
            </div>
            {taxBad && <div className="pf-bill-err">{taxType === 'br_cnpj' ? 'CNPJ inválido.' : 'CPF inválido.'}</div>}
          </div>
        </>
      )}

      {err && <div className="pf-bill-err" role="alert">{err}</div>}
      <div className="pf-btn-row" style={{ marginTop: 22, justifyContent: 'flex-end' }}>
        <button className="pf-ghost-btn" onClick={onClose} disabled={busy}>Cancelar</button>
        <button className="btn-primary" onClick={save} disabled={!ready || busy} aria-busy={busy}>
          {busy ? <><span className="pf-spin-sm" aria-hidden="true" /> Atualizando…</> : 'Atualizar'}
        </button>
      </div>
      <div className="pf-bill-secure"><Icon name="lock" size={12} /> Pagamento processado pelo Stripe. O Horos não guarda o número do cartão.</div>
    </BillModal>
  );
}

// Aviso quando parte das licenças novas não cabe mais no preço promocional
function _promoNoticeText(n) {
  const why = n.reason === 'sold_out' ? 'as vagas da promoção acabaram' : 'os 14 dias da promoção já passaram';
  return `Só ${n.promo_seats} licença(s) ficam no preço promocional: ${why}. As outras ${n.normal_seats} serão cobradas pelo preço normal (${_billMoney(n.normal_price)} por licença, por mês).`;
}

// ── Confirmação da troca (dentro do modal de upgrade): ciclo, assentos e prévia do valor de hoje
function BillChangePlan({ st, initPlan, initCycle, onDone, onCancel, toast }) {
  useBillPrices();
  const tMin = st.team_min || 3, tMax = st.team_max || 150;
  const plan = initPlan;
  const [cycle, setCycle] = React.useState(initCycle || st.cycle || 'monthly');
  const [seats, setSeats] = React.useState(st.seats || { std: tMin, prem: 0 });
  const [prev, setPrev] = React.useState(null);   // null | 'loading' | { due_now } | { error }
  const [busy, setBusy] = React.useState(false);

  const total = (seats.std || 0) + (seats.prem || 0);
  const seatsOk = plan !== 'team' || (total >= tMin && total <= tMax);
  const same = plan === st.plan && cycle === st.cycle &&
    (plan !== 'team' || ((st.seats?.std || 0) === seats.std && (st.seats?.prem || 0) === seats.prem));
  const order = { plan, cycle, seats: plan === 'team' ? seats : undefined };

  React.useEffect(() => {
    if (same || !seatsOk) { setPrev(null); return; }
    setPrev('loading');
    let alive = true;
    const t = setTimeout(async () => {
      try { const r = await _billApi('POST', { action: 'preview', ...order }); alive && setPrev(r); }
      catch (e) { alive && setPrev({ error: e.message }); }
    }, 350);
    return () => { alive = false; clearTimeout(t); };
  }, [cycle, seats.std, seats.prem]);

  const confirm = async () => {
    setBusy(true);
    try {
      const done = await _billApi('POST', { action: 'change', ...order });
      toast(done?.promo_notice ? _promoNoticeText(done.promo_notice) : 'Plano alterado para ' + BILL_PLAN[plan]);
      window.dispatchEvent(new CustomEvent('horos:plan-changed', { detail: { plan, status: st.status } }));
      if (String(plan).startsWith('dist_') !== String(st.plan || '').startsWith('dist_')) {
        try { localStorage.removeItem('horos-ws-cache'); } catch {}
        setTimeout(() => location.replace(String(plan).startsWith('dist_') ? 'leads.html' : 'dashboard.html'), 900);
      }
      onDone();
    } catch (e) { toast(e.message, 'error'); setBusy(false); }
  };

  const setSeat = (k, d) => setSeats(s => {
    const n = { ...s, [k]: Math.max(0, (s[k] || 0) + d) };
    return n.std + n.prem > tMax ? s : n;
  });
  const per = cycle === 'annually' ? 'ano' : 'mês';

  return (
    <div className="pf-bill-confirm">
      <div className="pf-bill-confirm-head">
        <span className="pf-bill-art sm">{BILL_ART[plan]}</span>
        <div>
          <div className="pf-bill-serif">Mudar para o plano {BILL_PLAN[plan]}</div>
          <div className="pf-sw-sub">{BILL_PLANS_INFO[plan].tagline}</div>
        </div>
      </div>

      <div className="pf-bill-label">Cobrança</div>
      <div className="pf-bill-seg" role="radiogroup" aria-label="Ciclo de cobrança">
        <button type="button" role="radio" aria-checked={cycle === 'monthly'} className={cycle === 'monthly' ? 'on' : ''} onClick={() => setCycle('monthly')}>Mensal</button>
        <button type="button" role="radio" aria-checked={cycle === 'annually'} className={cycle === 'annually' ? 'on' : ''} onClick={() => setCycle('annually')}>Anual <span className="pf-bill-save">economize até {Math.max(...["pro", "max", "team_std"].map(k => Math.round((1 - BILL_PRICES[k].annually / (BILL_PRICES[k].monthly * 12)) * 100)))}%</span></button>
      </div>

      {plan === 'team' && (
        <>
          <div className="pf-bill-label">Assentos <span className="pf-sw-sub">· mínimo {tMin}, máximo {tMax}</span></div>
          {[['std', 'Licenças padrão', BILL_PRICES.team_std], ['prem', 'Assentos Premium', BILL_PRICES.team_prem]].map(([k, label, pr]) => (
            <div key={k} className="pf-sw-row">
              <div style={{ flex: 1 }}>
                <div className="pf-sw-label">{label}</div>
                <div className="pf-sw-sub">{_billMoney(pr[cycle])} / {per} cada</div>
              </div>
              <div className="pf-bill-step">
                <button type="button" onClick={() => setSeat(k, -1)} disabled={!seats[k]} aria-label={`Menos ${label}`}>−</button>
                <span aria-live="polite">{seats[k] || 0}</span>
                <button type="button" onClick={() => setSeat(k, 1)} disabled={total >= tMax} aria-label={`Mais ${label}`}>+</button>
              </div>
            </div>
          ))}
          {!seatsOk && <div className="pf-bill-err">O plano Team precisa de pelo menos {tMin} assentos.</div>}
        </>
      )}

      <div className="pf-bill-sum">
        <div className="pf-sw-row">
          <div className="pf-sw-sub" style={{ flex: 1 }}>Plano atual</div>
          <div className="pf-sw-label">{BILL_PLAN[st.plan]} · {_billMoney(_billTotal(st.plan, st.cycle || 'monthly', st.seats))} / {st.cycle === 'annually' ? 'ano' : 'mês'}</div>
        </div>
        <div className="pf-sw-row">
          <div className="pf-sw-sub" style={{ flex: 1 }}>Novo valor</div>
          <div className="pf-sw-label">{_billMoney(_billTotal(plan, cycle, seats))} / {per}</div>
        </div>
        {!same && seatsOk && (
          <div className="pf-sw-row">
            <div className="pf-sw-sub" style={{ flex: 1 }}>Cobrado hoje</div>
            <div className="pf-sw-label">
              {prev === 'loading' ? <span className="pf-spin-sm" aria-label="Calculando" /> :
               prev && !prev.error ? _billMoney(prev.due_now, true) : '—'}
            </div>
          </div>
        )}
        <div className="pf-sw-sub" style={{ marginTop: 8 }}>
          {same ? 'Este já é o seu plano atual.' :
           st.status === 'trialing' ? 'Você está no teste grátis: a troca vale na hora e nada é cobrado agora.' :
           'Subiu de plano: cobramos hoje só a diferença proporcional. Desceu: a diferença vira crédito na próxima fatura.'}
        </div>
        {prev?.promo_notice && <div className="pf-bill-err">{_promoNoticeText(prev.promo_notice)}</div>}
        {prev?.error && <div className="pf-bill-err">{prev.error}</div>}
      </div>

      <div className="pf-btn-row" style={{ marginTop: 18, justifyContent: 'flex-end' }}>
        <button className="pf-ghost-btn" onClick={onCancel} disabled={busy}>Voltar aos planos</button>
        <button className="btn-primary" onClick={confirm} disabled={same || !seatsOk || busy || prev === 'loading'} aria-busy={busy}>
          {busy ? <><span className="pf-spin-sm" aria-hidden="true" /> Alterando…</> : 'Confirmar mudança'}
        </button>
      </div>
    </div>
  );
}

// ── Modal "Fazer upgrade" — mesmo visual da tela de preços (Para indivíduos | Team e Enterprise)
function BillUpgradeModal({ st, onClose, onDone, toast }) {
  useBillPrices();
  const [tab, setTab] = React.useState(st.plan === 'team' ? 'team' : String(st.plan || '').startsWith('dist_') ? 'dist' : 'personal');
  const [pick, setPick] = React.useState(null); // { plan, cycle }
  const cyc = st.cycle || 'monthly';
  const save = (m, y) => Math.round((1 - y / (m * 12)) * 100);

  const priceBlock = (id) => {
    const p = BILL_PRICES[id];
    const v = cyc === 'annually' ? p.annually / 12 : p.monthly;
    return (
      <div className="pf-up-price">
        <span className="amt">R$ {_billInt(v)}</span>
        <span className="meta">BRL / mês<br />{cyc === 'annually' ? `R$ ${_billInt(p.annually)} cobrado anualmente` : 'cobrado mensalmente'}</span>
      </div>
    );
  };

  const personalCard = (id) => {
    const info = BILL_PLANS_INFO[id];
    const cur = st.plan === id;
    const p = BILL_PRICES[id];
    return (
      <div key={id} className={`pf-up-card${id === 'max' || id === 'dist_30' ? ' pop' : ''}`}>
        <div className="pf-up-top">
          {id === 'dist_30' && <span className="pf-up-badge">Mais popular</span>}
          <span className="pf-bill-art">{BILL_ART[id]}</span>
          <div className="pf-up-name">{BILL_PLAN[id]}{cur && <span className="pf-bill-cur">Seu plano</span>}</div>
          <div className="pf-up-tag">{info.tagline}</div>
          {priceBlock(id)}
          {cur ? (
            cyc === 'monthly' ? (
              <>
                <div className="pf-up-note"><Icon name="info" size={14} /><div><b>Você está em um plano de cobrança mensal.</b><br />Pague anualmente para economizar {save(p.monthly, p.annually)}%.</div></div>
                <button className="pf-up-btn ghost" onClick={() => setPick({ plan: id, cycle: 'annually' })}>Obter plano {BILL_PLAN[id]} anual</button>
              </>
            ) : (
              <>
                <div className="pf-up-note"><Icon name="info" size={14} /><div><b>Você está no plano anual.</b><br />Renova em {_billDate(st.period_end)}.</div></div>
                <button className="pf-up-btn ghost" disabled>Seu plano atual</button>
              </>
            )
          ) : (
            <>
              <button className="pf-up-btn" onClick={() => setPick({ plan: id, cycle: cyc })}>Obter plano {BILL_PLAN[id]}</button>
              <div className="pf-up-fine">Sem compromisso · Cancele quando quiser</div>
            </>
          )}
        </div>
        <div className="pf-up-feats">
          <div className="pf-up-from">{info.from}</div>
          <ul>{info.features.map(f => <li key={f}><Icon name="check" size={14} />{f}</li>)}</ul>
        </div>
      </div>
    );
  };

  const teamCard = () => {
    const info = BILL_PLANS_INFO.team;
    const cur = st.plan === 'team';
    const seatRow = (label, desc, pr) => (
      <div className="pf-up-seat">
        <div className="pf-up-seat-h"><span>{label}</span><span className="amt">R$ {_billInt(pr.monthly)}<small> /mês</small></span></div>
        <div className="pf-up-fine left">{desc}</div>
        <div className="pf-up-fine left">R$ {_billInt(pr.annually / 12)}/mês com cobrança anual</div>
      </div>
    );
    return (
      <div className="pf-up-card">
        <div className="pf-up-top">
          <span className="pf-up-badge">3–150 usuários</span>
          <span className="pf-bill-art">{BILL_ART.team}</span>
          <div className="pf-up-name serif">Team{cur && <span className="pf-bill-cur">Seu plano</span>}</div>
          <div className="pf-up-tag">{info.tagline}</div>
          <div className="pf-up-box">
            {seatRow('Licença padrão', 'O essencial, como no Pro', BILL_PRICES.team_std)}
            <div className="pf-up-div" />
            {seatRow('Assento Premium', 'Tudo do Max: copiloto e automações', BILL_PRICES.team_prem)}
          </div>
          <button className="pf-up-btn" onClick={() => setPick({ plan: 'team', cycle: cyc })}>{cur ? 'Ajustar assentos' : 'Obter plano Team'}</button>
          <div className="pf-up-fine">Mínimo de 3 assentos · Cancele quando quiser</div>
        </div>
        <div className="pf-up-feats">
          <div className="pf-up-from">{info.from}</div>
          <ul>{info.features.map(f => <li key={f}><Icon name="check" size={14} />{f}</li>)}</ul>
        </div>
      </div>
    );
  };

  const entCard = () => {
    const info = BILL_PLANS_INFO.enterprise;
    return (
      <div className="pf-up-card">
        <div className="pf-up-top">
          <span className="pf-up-badge">20+ usuários</span>
          <span className="pf-bill-art">{BILL_ART.enterprise}</span>
          <div className="pf-up-name serif">Enterprise</div>
          <div className="pf-up-tag">{info.tagline}</div>
          <div className="pf-up-box">
            <div className="pf-up-seat-h"><span>Preço por assento + implantação</span></div>
            <div className="pf-up-fine left">A partir de {_billMoney(BILL_PRICES.ent_seat.annually / 12)} por assento/mês, contrato anual. Boleto ou cartão.</div>
          </div>
          <a className="pf-up-btn" href="pricing.html?checkout=enterprise&step=1&from=app">Contratar Enterprise</a>
          <div className="pf-up-fine">Ou <a href="support.html?contato=1&assunto=vendas&page=profile.html&titulo=Plano%20Enterprise">fale com a gente</a></div>
        </div>
        <div className="pf-up-feats">
          <div className="pf-up-from">{info.from}</div>
          <ul>{info.features.map(f => <li key={f}><Icon name="check" size={14} />{f}</li>)}</ul>
        </div>
      </div>
    );
  };

  return (
    <BillModal onClose={onClose} xl label="Fazer upgrade" title="Fazer upgrade">
      <div className="pf-up-body">
        {pick ? (
          <BillChangePlan st={st} initPlan={pick.plan} initCycle={pick.cycle} toast={toast}
                          onCancel={() => setPick(null)} onDone={onDone} />
        ) : (
          <>
            <div className={`pf-up-head${tab === 'team' ? ' with-help' : ''}`}>
              <h2 className="pf-bill-serif xl">Planos que crescem com você</h2>
              {tab === 'team' && (
                <a className="pf-up-help" href={`support.html?contato=1&assunto=vendas&page=profile.html&titulo=${encodeURIComponent('Dúvida sobre os planos Team e Enterprise')}`}>
                  <Icon name="message" size={15} /><span>Dúvidas? Converse com nosso especialista em compras</span><span className="go"><Icon name="arrRightS" size={14} /></span>
                </a>
              )}
            </div>
            <div className="pf-up-tabs" role="tablist" aria-label="Tipo de plano">
              <button role="tab" aria-selected={tab === 'personal'} className={tab === 'personal' ? 'on' : ''} onClick={() => setTab('personal')}>Para indivíduos</button>
              <button role="tab" aria-selected={tab === 'team'} className={tab === 'team' ? 'on' : ''} onClick={() => setTab('team')}>Team e Enterprise</button>
              <button role="tab" aria-selected={tab === 'dist'} className={tab === 'dist' ? 'on' : ''} onClick={() => setTab('dist')}>Só distribuição</button>
            </div>
            <div className={`pf-up-grid${tab === 'dist' ? ' three' : ''}`} key={tab}>
              {tab === 'personal' ? ['pro', 'max'].map(personalCard)
                : tab === 'dist' ? ['dist_10', 'dist_30', 'dist_unl'].map(personalCard)
                : <>{teamCard()}{entCard()}</>}
            </div>
            <div className="pf-up-foot">Preços em reais. Subiu de plano: cobramos só a diferença proporcional. Desceu: a diferença vira crédito.</div>
          </>
        )}
      </div>
    </BillModal>
  );
}

// ── Cancelamento em 3 etapas: motivo → o que você perde → confirmar digitando o nome completo
const BILL_REASONS = [
  ['too_expensive', 'Está caro para mim'],
  ['unused', 'Não estou usando o suficiente'],
  ['missing_features', 'Falta um recurso de que preciso'],
  ['switched_service', 'Vou usar outro sistema'],
  ['too_complex', 'É difícil de usar'],
  ['low_quality', 'Tive problemas ou instabilidade'],
  ['customer_service', 'Não fui bem atendido'],
  ['other', 'Outro motivo'],
];
function BillCancelModal({ st, fullName, onClose, onDone, onDowngrade, toast }) {
  const [step, setStep] = React.useState(1);
  const [feedback, setFeedback] = React.useState('');
  const [detail, setDetail] = React.useState('');
  const [typed, setTyped] = React.useState('');
  const [busy, setBusy] = React.useState(false);
  const info = BILL_PLANS_INFO[st.plan] || BILL_PLANS_INFO.pro;
  const needDetail = feedback === 'other' || feedback === 'missing_features';
  const step1ok = feedback && (!needDetail || detail.trim().length >= 5);
  const nameOk = fullName && _billNorm(typed) === _billNorm(fullName);
  const canDowngrade = st.plan === 'max' || st.plan === 'team';

  const doCancel = async () => {
    setBusy(true);
    try {
      const label = (BILL_REASONS.find(r => r[0] === feedback) || [])[1] || '';
      await _billApi('POST', { action: 'cancel', feedback, reason: [label, detail.trim()].filter(Boolean).join(' — ') });
      toast('Cancelamento agendado');
      onDone();
    } catch (e) { toast(e.message, 'error'); setBusy(false); }
  };

  return (
    <BillModal onClose={onClose} busy={busy} wide label="Cancelar assinatura" title="Cancelar assinatura">
      <div className="pf-bill-steps" aria-label={`Etapa ${step} de 3`}>
        {[1, 2, 3].map(n => <span key={n} className={n <= step ? 'on' : ''} />)}
        <em>Etapa {step} de 3</em>
      </div>

      {step === 1 && (
        <>
          <div className="pf-bill-q">Por que você quer cancelar?</div>
          <div className="pf-sw-sub" style={{ marginBottom: 12 }}>Sua resposta vai direto para o time do Horos.</div>
          <div className="pf-bill-reasons" role="radiogroup" aria-label="Motivo do cancelamento">
            {BILL_REASONS.map(([id, label]) => (
              <label key={id} className={`pf-bill-reason${feedback === id ? ' on' : ''}`}>
                <input type="radio" name="pf-bill-reason" value={id} checked={feedback === id} onChange={() => setFeedback(id)} />
                <span>{label}</span>
              </label>
            ))}
          </div>
          <div className="pf-bill-field">
            <label htmlFor="pf-bill-detail">{needDetail ? 'Conte mais (obrigatório)' : 'Quer contar mais? (opcional)'}</label>
            <PTextarea value={detail} onChange={setDetail} rows={3} maxLen={500}
                       placeholder={feedback === 'missing_features' ? 'Qual recurso faltou?' : 'O que poderíamos ter feito melhor?'} />
          </div>
        </>
      )}

      {step === 2 && (
        <>
          <div className="pf-bill-q">Antes de ir: o que você perde</div>
          <div className="pf-sw-sub" style={{ marginBottom: 12 }}>
            {st.status === 'trialing'
              ? `O acesso termina em ${_billDate(st.period_end)}, no fim do teste grátis. Nada será cobrado.`
              : `Você usa o Horos até ${_billDate(st.period_end)}. Depois disso, a ${segTerm('company', 'imobiliária')} perde o acesso ao app.`}
          </div>
          <ul className="pf-bill-lose">
            {info.features.slice(0, 7).map(f => <li key={f}><Icon name="close" size={13} />{f}</li>)}
            <li><Icon name="close" size={13} />Toda a equipe da {segTerm('company', 'imobiliária')} fica sem acesso ao CRM e às conversas</li>
          </ul>
          {canDowngrade && (
            <div className="pf-bill-offer">
              <div>
                <div className="pf-sw-label">Prefere pagar menos?</div>
                <div className="pf-sw-sub">Troque para o plano Pro por {_billMoney(BILL_PRICES.pro.monthly)}/mês e mantenha seus leads e conversas.</div>
              </div>
              <button className="pf-ghost-btn" onClick={onDowngrade}>Ver planos</button>
            </div>
          )}
        </>
      )}

      {step === 3 && (
        <>
          <div className="pf-bill-q">Confirme o cancelamento</div>
          <div className="pf-sw-sub" style={{ marginBottom: 14 }}>
            Para confirmar, digite seu nome completo: <b className="pf-bill-name">{fullName}</b>
          </div>
          <input className="pf-input" value={typed} onChange={e => setTyped(e.target.value)} placeholder="Seu nome completo"
                 aria-label="Digite seu nome completo para confirmar" autoComplete="off" autoFocus />
          {typed && !nameOk && <div className="pf-bill-err">O nome não confere.</div>}
          <div className="pf-bill-alert neutral" style={{ marginTop: 14 }}>
            A assinatura não será renovada. Até {_billDate(st.period_end)} você pode reativar aqui mesmo, sem nova cobrança.
          </div>
        </>
      )}

      <div className="pf-btn-row pf-bill-cancel-foot">
        <button className="btn-primary" onClick={onClose} disabled={busy}>Manter assinatura</button>
        <div style={{ flex: 1 }} />
        {step > 1 && <button className="pf-ghost-btn" onClick={() => setStep(s => s - 1)} disabled={busy}>Voltar</button>}
        {step < 3 ? (
          <button className="pf-ghost-btn" onClick={() => setStep(s => s + 1)} disabled={step === 1 && !step1ok}>Continuar cancelamento</button>
        ) : (
          <button className="pf-bill-danger" onClick={doCancel} disabled={!nameOk || busy} aria-busy={busy}>
            {busy ? <><span className="pf-spin-sm" aria-hidden="true" /> Cancelando…</> : 'Cancelar assinatura'}
          </button>
        )}
      </div>
    </BillModal>
  );
}

function AssinaturaCard({ toast, profile }) {
  useBillPrices();
  const [st, setSt] = React.useState(null);
  const [err, setErr] = React.useState('');
  const [modal, setModal] = React.useState(null); // null · 'upgrade' · 'card' · 'cancel'
  const [busy, setBusy] = React.useState('');
  const blocked = /[?&]bloqueado=1\b/.test(window.location.search);
  const fullName = [profile?.nome, profile?.sobrenome].filter(Boolean).join(' ').trim();

  const load = React.useCallback(async () => {
    setErr('');
    try { setSt(await _billApi('GET', null, '?details=1')); }
    catch (e) { setErr('Não foi possível carregar a assinatura.'); }
  }, []);
  React.useEffect(() => { load(); }, [load]);
  // Veio de um aviso "Disponível no plano Max" (profile.html?aba=assinatura&upgrade=1): abre o upgrade
  React.useEffect(() => {
    if (st && st.can_manage !== false && /[?&]upgrade=1\b/.test(window.location.search)) setModal(m => m || 'upgrade');
  }, [st]);

  const resume = async () => {
    setBusy('resume');
    try { await _billApi('POST', { action: 'resume' }); toast('Assinatura reativada'); await load(); }
    catch (e) { toast(e.message, 'error'); }
    setBusy('');
  };

  if (err) return (
    <PCard title="Assinatura" icon="wallet">
      <div className="pf-sw-sub" style={{ marginBottom: 12 }}>{err}</div>
      <button className="pf-ghost-btn" onClick={load}>Tentar de novo</button>
    </PCard>
  );
  if (!st) return (
    <PCard title="Assinatura" icon="wallet">
      <div className="pf-bill-skel" aria-label="Carregando" role="status"><span /><span /><span /></div>
    </PCard>
  );

  const live = BILL_LIVE.includes(st.status);
  const status = BILL_STATUS[st.status];
  const seatsN = st.seats ? (st.seats.std || 0) + (st.seats.prem || 0) : 0;
  const per = st.cycle === 'annually' ? 'ano' : 'mês';
  const pm = st.payment_method;
  const paused = st.status === 'paused';
  const trialNoCard = st.status === 'trialing' && !pm && !st.cancel_at_end;

  return (
    <>
      {blocked && !live && (
        <div className="pf-bill-alert" role="alert" style={{ marginTop: 0 }}>
          <b>A assinatura da sua {segTerm('company', 'imobiliária')} não está ativa.</b>{' '}
          {st.can_manage ? 'Escolha um plano para voltar a usar o Horos.' : `Peça para a diretoria da ${segTerm('company', 'imobiliária')} escolher um plano para liberar o acesso.`}
        </div>
      )}

      <PCard title="Assinatura" icon="wallet">
        {live ? (
          <>
            <div className="pf-bill-hero">
              <div>
                <div className="pf-bill-plan-name">
                  Plano {BILL_PLAN[st.plan] || st.plan}
                  <span className={`pf-bill-chip${status?.bad ? ' bad' : ''}`}>{st.cancel_at_end ? 'Cancelamento agendado' : (status?.t || st.status)}</span>
                </div>
                <div className="pf-sw-sub">
                  {_billMoney(_billTotal(st.plan, st.cycle || 'monthly', st.seats))} / {per}
                  {seatsN ? ` · ${seatsN} assentos` : ''} · cobrança {st.cycle === 'annually' ? 'anual' : 'mensal'}
                </div>
              </div>
              {st.can_manage && !st.cancel_at_end && !paused && st.plan !== 'enterprise' && (
                <button className="btn-primary" onClick={() => setModal('upgrade')}>Mudar plano</button>
              )}
              {st.can_manage && st.plan === 'enterprise' && (
                <a className="pf-ghost-btn" href={`support.html?contato=1&assunto=assinatura&page=profile.html&titulo=${encodeURIComponent('Alterar plano Enterprise')}`}>Falar com o suporte</a>
              )}
            </div>

            <div className="pf-sw-row">
              <div className="pf-sw-sub" style={{ flex: 1 }}>
                {st.cancel_at_end ? 'Acesso até' : paused ? 'Teste terminou em' : trialNoCard ? 'Fim do teste grátis' : st.status === 'trialing' ? 'Fim do teste · 1ª cobrança' : 'Próxima cobrança'}
              </div>
              <div className="pf-sw-label">
                {_billDate(st.period_end)}
                {!st.cancel_at_end && !paused && !trialNoCard && st.upcoming && <span className="pf-sw-sub"> · {_billMoney(st.upcoming.total, true)}</span>}
              </div>
            </div>
            {trialNoCard && (
              <div className="pf-bill-alert neutral pf-bill-trial">
                <div><b>Você está no teste grátis, sem cartão.</b> Para continuar usando o Horos depois de {_billDate(st.period_end)}, cadastre o cartão — nada é cobrado antes do fim do teste.</div>
                {st.can_manage
                  ? <button className="btn-primary" onClick={() => setModal('card')}>Cadastrar cartão</button>
                  : <span className="pf-sw-sub">Peça para a diretoria cadastrar o cartão.</span>}
              </div>
            )}
            {paused && (
              <div className="pf-bill-alert pf-bill-trial" role="alert">
                <div><b>Seu teste grátis terminou.</b> Cadastre o cartão para voltar a usar o Horos — seus dados estão guardados e a cobrança do 1º período é feita na hora.</div>
                {st.can_manage
                  ? <button className="btn-primary" onClick={() => setModal('card')}>Cadastrar cartão e continuar</button>
                  : <span className="pf-sw-sub">Peça para a diretoria cadastrar o cartão para liberar o acesso.</span>}
              </div>
            )}
            {st.status === 'past_due' && (
              <div className="pf-bill-alert">Não conseguimos cobrar o cartão. Troque o cartão abaixo — tentamos cobrar de novo na hora.</div>
            )}
            {st.cancel_at_end && (
              <div className="pf-bill-alert neutral">A assinatura não será renovada. Você continua com acesso até {_billDate(st.period_end)}.</div>
            )}
            {!st.can_manage && (
              <div className="pf-sw-sub" style={{ marginTop: 12 }}>Só a diretoria da {segTerm('company', 'imobiliária')} pode alterar o plano.</div>
            )}
          </>
        ) : (
          <div className="pf-bill-empty">
            <div className="pf-sw-label">{st.status === 'canceled' ? 'A assinatura foi encerrada.' : `Sua ${segTerm('company', 'imobiliária')} ainda não tem um plano.`}</div>
            <div className="pf-sw-sub" style={{ margin: '4px 0 14px' }}>
              {st.had_subscription ? 'Escolha um plano para voltar a usar todos os recursos.' : 'Todos os planos começam com 14 dias grátis.'}
            </div>
            {st.can_manage
              ? <a className="btn-primary" href="pricing.html?from=app" style={{ textDecoration: 'none', display: 'inline-flex' }}>Escolher plano</a>
              : <div className="pf-sw-sub">Só a diretoria da {segTerm('company', 'imobiliária')} pode contratar o plano.</div>}
          </div>
        )}
      </PCard>

      {st.can_manage && st.has_customer && (
        <PCard title="Forma de pagamento" icon="wallet">
          <div className="pf-sw-row">
            <div style={{ flex: 1 }}>
              {pm ? (
                <>
                  <div className="pf-sw-label">{_BRAND[pm.brand] || pm.brand}{pm.last4 ? ` •••• ${pm.last4}` : ''}</div>
                  {pm.exp_month && <div className="pf-sw-sub">Vence em {String(pm.exp_month).padStart(2, '0')}/{pm.exp_year}</div>}
                  {st.billing?.tax_id && <div className="pf-sw-sub">{st.billing.tax_id.type === 'br_cnpj' ? 'CNPJ' : 'CPF'} {st.billing.tax_id.value}</div>}
                </>
              ) : <div className="pf-sw-sub">Nenhum cartão cadastrado.</div>}
            </div>
            {live && <button className="pf-ghost-btn" onClick={() => setModal('card')}>{pm ? 'Atualizar' : 'Adicionar cartão'}</button>}
          </div>
        </PCard>
      )}

      {st.can_manage && st.has_customer && (
        <PCard title="Histórico de faturas · últimos 6 meses" icon="sheet" action={st.invoices?.length ? <span className="pf-bill-count">{st.invoices.length}</span> : null}>
          {st.details_error ? (
            <div className="pf-sw-sub">Não foi possível carregar as faturas agora.</div>
          ) : !st.invoices?.length ? (
            <div className="pf-sw-sub">Nenhuma fatura ainda.</div>
          ) : (
            <div className="pf-bill-table-wrap">
              <table className="pf-bill-table">
                <thead><tr><th>Data</th><th>Descrição</th><th className="num">Valor</th><th>Situação</th><th><span className="sr-only">Ações</span></th></tr></thead>
                <tbody>
                  {st.invoices.map(i => (
                    <tr key={i.id}>
                      <td>{_billDate(i.created, true)}</td>
                      <td className="desc">
                        <div>{i.description || 'Assinatura Horos'}</div>
                        {i.number && <div className="pf-sw-sub">Nº {i.number}</div>}
                      </td>
                      <td className="num">{_billMoney(i.total, true)}</td>
                      <td><span className={`pf-bill-inv ${i.status}`}>{BILL_INV[i.status] || i.status}</span></td>
                      <td className="act">
                        {i.status === 'open' && i.url && <a href={i.url} target="_blank" rel="noopener">Pagar</a>}
                        {i.url && i.status !== 'open' && <a href={i.url} target="_blank" rel="noopener">Ver</a>}
                        {i.pdf && <a href={i.pdf} target="_blank" rel="noopener" aria-label={`Baixar PDF da fatura ${i.number || ''}`}>PDF</a>}
                      </td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>
          )}
        </PCard>
      )}

      {live && !paused && st.can_manage && (
        st.cancel_at_end ? (
          <PCard title="Reativar assinatura" icon="refresh">
            <div className="pf-sw-row">
              <div className="pf-sw-sub" style={{ flex: 1 }}>Mudou de ideia? Reative e a assinatura segue renovando normalmente, sem cobrança agora.</div>
              <button className="btn-primary" onClick={resume} disabled={!!busy} aria-busy={busy === 'resume'}>
                {busy === 'resume' ? <><span className="pf-spin-sm" aria-hidden="true" /> Reativando…</> : 'Reativar'}
              </button>
            </div>
          </PCard>
        ) : (
          <div className="pf-bill-cancel-link">
            <button onClick={() => setModal('cancel')}>Cancelar assinatura</button>
          </div>
        )
      )}

      {modal === 'upgrade' && (
        <BillUpgradeModal st={st} toast={toast} onClose={() => setModal(null)} onDone={() => { setModal(null); load(); }} />
      )}
      {modal === 'card' && (
        <BillCardModal billing={st.billing} onClose={() => setModal(null)}
                       onSaved={(pmSaved, r) => {
                         setModal(null);
                         // Assinatura pausada (teste acabou) foi retomada → volta para o app
                         if (r && r.resumed) { toast('Assinatura ativada'); setTimeout(() => { window.location.href = 'dashboard.html?assinatura=ok'; }, 700); return; }
                         toast('Forma de pagamento atualizada'); load();
                       }} />
      )}
      {modal === 'cancel' && (
        <BillCancelModal st={st} fullName={fullName} toast={toast}
                         onClose={() => setModal(null)}
                         onDowngrade={() => setModal('upgrade')}
                         onDone={() => { setModal(null); load(); }} />
      )}
    </>
  );
}

// Caches por usuário: na mesma máquina outra conta não pode herdar foto/dados
const pfAvatarKey = () => 'horos-avatar:' + (window.HOROS_USER?.id || '');
const pfExtraKey = () => 'horos-perfil-extra:' + (window.HOROS_USER?.id || '');

// ─── ZONA DE PERIGO ───────────────────────────────────────────────────────────
// ── Privacidade e dados (2026-10-13, LGPD) ──────────────────────────────────────
// Exporta no navegador, com o acesso de quem está logado (mesmas regras de cargo do app):
// leads (planilha + JSON com a ficha completa), conversas, mensagens, agenda e respostas rápidas.
// Prazos e papéis em dpa.html (Acordo de Tratamento de Dados).
const _PD_CSV = rows => '﻿' + rows.map(r => r.map(v => {
  const s = v == null ? '' : typeof v === 'object' ? JSON.stringify(v) : String(v);
  return /[";\n\r]/.test(s) ? '"' + s.replace(/"/g, '""') + '"' : s;
}).join(';')).join('\r\n');

async function _pdLoadZip() {
  if (window.JSZip) return window.JSZip;
  await new Promise((ok, fail) => {
    const s = document.createElement('script');
    s.src = 'https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js';
    s.onload = ok; s.onerror = () => fail(new Error('Não foi possível carregar o compactador. Confira a internet.'));
    document.head.appendChild(s);
  });
  return window.JSZip;
}

// Pagina de 1000 em 1000 (limite do banco) — build(q) monta a consulta
async function _pdAll(build, onPage) {
  const out = [];
  for (let from = 0; ; from += 1000) {
    const { data, error } = await build().range(from, from + 999);
    if (error) throw new Error(error.message);
    out.push(...(data || []));
    onPage && onPage(out.length);
    if (!data || data.length < 1000) break;
  }
  return out;
}

function PrivacidadeDadosCard({ toast }) {
  const [busy, setBusy] = React.useState(false);
  const [step, setStep] = React.useState('');

  async function exportAll() {
    if (busy) return;
    setBusy(true);
    try {
      const sb = window.HOROS_SB;
      const uid = window.horosActUid ? window.horosActUid() : window.HOROS_USER?.id;
      const JSZip = await _pdLoadZip();

      setStep('Leads…');
      const leads = await _pdAll(() => sb.from('leads')
        .select('id,name,phone,email,cat,temp,col,value,score,broker,archived,created_at,updated_at,closed_at,data,docs')
        .order('created_at'), n => setStep(`Leads… ${n.toLocaleString('pt-BR')}`));

      setStep('Conversas…');
      const convs = uid ? await _pdAll(() => sb.from('conversations')
        .select('id,wa_jid,contact_name,contact_phone,labels,archived,created_at,last_message_at')
        .eq('user_id', uid).order('created_at')) : [];

      setStep('Mensagens…');
      const msgs = uid ? await _pdAll(() => sb.from('messages')
        .select('conversation_id,direction,type,body,status,media_url,sent_at')
        .eq('user_id', uid).order('sent_at'), n => setStep(`Mensagens… ${n.toLocaleString('pt-BR')}`)) : [];

      setStep('Agenda…');
      const events = await _pdAll(() => sb.from('events')
        .select('title,cat,client,phone,location,start_at,end_at,sub,created_at')
        .eq('user_id', window.HOROS_USER?.id).order('start_at'));
      const replies = await _pdAll(() => sb.from('quick_replies').select('shortcut,message,created_at').order('created_at')).catch(() => []);

      setStep('Montando o arquivo…');
      const cols = (window.loadKanbanCols ? window.loadKanbanCols() : []);
      const stage = id => (cols.find(c => c.id === id)?.title) || id || '';
      const dt = v => v ? new Date(v).toLocaleString('pt-BR') : '';
      const convById = new Map(convs.map(c => [c.id, c]));

      const zip = new JSZip();
      zip.file('leads.csv', _PD_CSV([
        ['ID', 'Nome', 'Telefone', 'E-mail', 'Etapa', 'Temperatura', 'Região', 'Valor', 'Score', segTerm('Agent', 'Corretor'), 'Arquivado', 'Criado em', 'Atualizado em', 'Fechado em'],
        ...leads.map(l => [l.id, l.name, l.phone, l.email, stage(l.col), l.temp, l.cat, l.value, l.score, l.broker, l.archived ? 'sim' : 'não', dt(l.created_at), dt(l.updated_at), dt(l.closed_at)]),
      ]));
      zip.file('leads-ficha-completa.json', JSON.stringify(leads, null, 2));
      zip.file('conversas.csv', _PD_CSV([
        ['ID', 'Contato', 'Telefone', 'WhatsApp', 'Etiquetas', 'Arquivada', 'Criada em', 'Última mensagem'],
        ...convs.map(c => [c.id, c.contact_name, c.contact_phone, c.wa_jid, (c.labels || []).join(','), c.archived ? 'sim' : 'não', dt(c.created_at), dt(c.last_message_at)]),
      ]));
      zip.file('mensagens.csv', _PD_CSV([
        ['Conversa', 'Contato', 'Telefone', 'Data', 'Direção', 'Tipo', 'Texto', 'Status', 'Arquivo'],
        ...msgs.map(m => { const c = convById.get(m.conversation_id) || {};
          return [m.conversation_id, c.contact_name, c.contact_phone, dt(m.sent_at), m.direction === 'out' ? 'enviada' : 'recebida', m.type, m.body, m.status, m.media_url]; }),
      ]));
      zip.file('agenda.csv', _PD_CSV([
        ['Título', 'Tipo', 'Cliente', 'Telefone', 'Local', 'Início', 'Fim', 'Observações'],
        ...events.map(e => [e.title, e.cat, e.client, e.phone, e.location, dt(e.start_at), dt(e.end_at), e.sub]),
      ]));
      zip.file('respostas-rapidas.csv', _PD_CSV([['Atalho', 'Mensagem'], ...replies.map(r => [r.shortcut, r.message])]));
      zip.file('LEIA-ME.txt', [
        'Exportação de dados do Horos',
        `Gerada em ${new Date().toLocaleString('pt-BR')} por ${window.HOROS_USER?.email || ''}`,
        '',
        `leads.csv — ${leads.length} leads que você tem acesso, em planilha (abre no Excel)`,
        'leads-ficha-completa.json — os mesmos leads com a ficha completa e os documentos (links)',
        `conversas.csv — ${convs.length} conversas do WhatsApp`,
        `mensagens.csv — ${msgs.length} mensagens`,
        `agenda.csv — ${events.length} compromissos`,
        'respostas-rapidas.csv — suas respostas rápidas',
        '',
        'Os arquivos de documentos e mídias ficam nos links das colunas; baixe-os enquanto a assinatura estiver ativa.',
        'Como tratamos esses dados: https://horos.sale/dpa',
      ].join('\r\n'));

      const blob = await zip.generateAsync({ type: 'blob', compression: 'DEFLATE' });
      const a = document.createElement('a');
      a.href = URL.createObjectURL(blob);
      a.download = `horos-dados-${new Date().toISOString().slice(0, 10)}.zip`;
      document.body.appendChild(a); a.click(); a.remove();
      setTimeout(() => URL.revokeObjectURL(a.href), 2000);
      toast(`Exportação pronta: ${leads.length} leads, ${msgs.length} mensagens`);
    } catch (e) {
      toast(e.message || 'Não foi possível exportar.', 'error');
    } finally {
      setBusy(false); setStep('');
    }
  }

  return (
    <PCard title="Privacidade e dados" icon="shield">
      <div className="pf-pd-text">
        <p>Baixe uma cópia de tudo o que você tem acesso no Horos: leads com a ficha completa, conversas e mensagens do WhatsApp, agenda e respostas rápidas. O arquivo sai em .zip com planilhas que abrem no Excel.</p>
        <p>Quando a assinatura termina, os dados ficam guardados por <b>90 dias</b> para voltar a assinar ou exportar; depois são excluídos. Detalhes no <a href="dpa.html" target="_blank" rel="noopener">Acordo de Tratamento de Dados</a> e na <a href="privacy.html" target="_blank" rel="noopener">Política de Privacidade</a>.</p>
      </div>
      <div className="pf-btn-row" style={{ marginTop: 14 }}>
        <button className="btn-primary" onClick={exportAll} disabled={busy} aria-busy={busy}>
          {busy ? <><span className="pf-spin-sm" aria-hidden="true" /> {step || 'Exportando…'}</> : <><Icon name="sheet" size={14} /> Exportar dados</>}
        </button>
      </div>
    </PCard>
  );
}

function ZonaDePerigoCard({ toast }) {
  const [confirms, setConfirms] = React.useState({});
  const timers = React.useRef({});

  const ACTIONS = [
    {
      id:'avatar',
      label:'Remover foto de perfil',
      sub:'Volta ao avatar com as iniciais do seu nome',
      exec: async () => { const uid = window.HOROS_USER?.id; if (uid) { await window.HOROS_SB.from('profiles').update({ avatar_url: null }).eq('id', uid); } localStorage.removeItem(pfAvatarKey()); toast('Foto de perfil removida'); setTimeout(() => window.location.reload(), 900); },
    },
    {
      id:'signout',
      label:'Sair de todos os dispositivos',
      sub:'Encerra todas as sessões ativas em todos os aparelhos imediatamente',
      exec: async () => { await window.HOROS_SB.auth.signOut({ scope:'global' }); window.location.href = 'login.html'; },
    },
    {
      id:'delete',
      label:'Excluir conta',
      sub:'Remove permanentemente sua conta e todos os dados. Esta ação é irreversível.',
      critical: true,
      exec: () => { location.href = 'support.html?contato=1&assunto=privacidade&page=profile.html&titulo=' + encodeURIComponent('Excluir minha conta'); },
    },
  ];

  const click = (a) => {
    const cur = confirms[a.id] || 0;
    if (cur < 2) {
      setConfirms(c => ({ ...c, [a.id]: cur + 1 }));
      clearTimeout(timers.current[a.id]);
      timers.current[a.id] = setTimeout(() => setConfirms(c => ({ ...c, [a.id]: 0 })), 4500);
    } else {
      clearTimeout(timers.current[a.id]);
      setConfirms(c => ({ ...c, [a.id]: 0 }));
      a.exec();
    }
  };

  return (
    <PCard title="Zona de Perigo" icon="trash" danger>
      <div className="pf-danger-list" role="list">
        {ACTIONS.map(a => (
          <div key={a.id} className={`pf-ditem${a.critical ? ' pf-ditem--critical' : ''}`} role="listitem">
            <div style={{ flex:1 }}>
              <div className="pf-ditem-label">{a.label}</div>
              <div className="pf-ditem-sub">{a.sub}</div>
            </div>
            <button
              className={`pf-danger-btn${confirms[a.id] ? ' pf-danger-btn--armed' : ''}`}
              onClick={() => click(a)}
              aria-label={confirms[a.id] ? `Confirmar: ${a.label}` : a.label}
            >
              {confirms[a.id]
                ? <><svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg> Confirmar</>
                : a.label
              }
            </button>
          </div>
        ))}
      </div>
    </PCard>
  );
}

// ─── MAIN PAGE ────────────────────────────────────────────────────────────────
function PerfilPage({ theme, onThemeChange }) {
  const [loading, setLoading] = React.useState(true);
  const [profile, setProfile] = React.useState(null);
  const [editing, setEditing] = React.useState(false);
  const [editForm, setEditForm] = React.useState({});
  const [saving, setSaving] = React.useState(false);
  const [pendingAvatar, setPendingAvatar] = React.useState(null); // null=no change, ''=remove, 'data:...'=new photo
  const { toasts, add: toast } = useToast();
  const [activeTab, setActiveTab] = React.useState(() =>
    ({ assinatura: 'assinatura', ia: 'ia', dados: 'dados', notificacoes: 'notificacoes' })[new URLSearchParams(window.location.search).get('aba')] || 'perfil');

  // Aba Assinatura só para quem controla os pagamentos (ceo/diretor/super, quem criou a imobiliária ou quem
  // está sozinho nela — mesma regra do servidor, canManageBilling). null = ainda conferindo.
  const blockedParam = /[?&]bloqueado=1\b/.test(window.location.search);
  const [canBill, setCanBill] = React.useState(() =>
    ['ceo', 'diretor', 'super'].includes(window.HOROS_WORKSPACE?.role) ? true : null);
  React.useEffect(() => {
    if (canBill === true) return;
    let alive = true;
    _billApi('GET').then(j => { if (alive) setCanBill(!!j.can_manage); }).catch(() => { if (alive) setCanBill(false); });
    return () => { alive = false; };
  }, []);
  React.useEffect(() => { if (canBill === false && activeTab === 'assinatura') setActiveTab('perfil'); }, [canBill, activeTab]);

  const TABS = [
    { id: 'perfil',       label: 'Perfil',         icon: 'user'     },
    { id: 'seguranca',    label: 'Segurança',       icon: 'lock'     },
    { id: 'preferencias', label: 'Preferências',    icon: 'settings' },
    { id: 'notificacoes', label: 'Notificações',    icon: 'bell'     },
    { id: 'ia',           label: 'Minha IA',        icon: 'ai'       },
    ...(canBill ? [{ id: 'assinatura',   label: 'Assinatura',      icon: 'wallet'   }] : []),
    { id: 'dados',        label: 'Privacidade e dados', icon: 'shield' },
    { id: 'perigo',       label: 'Zona de Perigo',  icon: 'trash', danger: true },
  ];

  const COLORS = ['#1F93FF','#007dff','#10b981','#f97316','#ec4899','#8b5cf6','#14b8a6'];

  // Nome do plano da imobiliária (get_my_workspace) — atualiza quando o workspace chega/muda
  React.useEffect(() => {
    let alive = true;
    window.HOROS_WORKSPACE_PROMISE?.then(ws => { if (alive && ws) setProfile(p => p && ({ ...p, ...planInfo(ws) })); });
    const onPlan = (e) => setProfile(p => p && ({ ...p, ...planInfo({ plan: e.detail.plan, plan_status: e.detail.status }) }));
    window.addEventListener('horos:plan-changed', onPlan);
    return () => { alive = false; window.removeEventListener('horos:plan-changed', onPlan); };
  }, []);

  React.useEffect(() => {
    const init = async () => {
      try {
        const sb = window.HOROS_SB;
        const u = window.HOROS_USER;
        if (!sb || !u) { setTimeout(init, 400); return; }

        const { data } = await sb.from('profiles').select('*').eq('id', u.id).single();
        const extra = JSON.parse(localStorage.getItem(pfExtraKey()) || '{}');
        const avatarUrl = data ? (data.avatar_url || '') : (localStorage.getItem(pfAvatarKey()) || '');
        if (data) { if (data.avatar_url) localStorage.setItem(pfAvatarKey(), data.avatar_url); else localStorage.removeItem(pfAvatarKey()); }
        localStorage.removeItem('horos-avatar');
        const meta = u.user_metadata || {};
        const nome = data?.nome || meta.nome || '';
        const sob = data?.sobrenome || meta.sobrenome || '';
        const color = COLORS[u.id.charCodeAt(0) % COLORS.length];
        const initials = ((nome && sob) ? nome[0]+sob[0] : nome[0] || u.email[0] || '?').toUpperCase();

        setProfile({
          id: u.id, nome, sobrenome: sob,
          email: u.email,
          cargo: data?.cargo || meta.cargo || '',
          area: data?.area || meta.area || '',
          cpf_cnpj: data?.cpf_cnpj || '',
          telefone: extra.telefone || '',
          website: extra.website || '',
          cidade: extra.cidade || '',
          estado: extra.estado || '',
          bio: extra.bio || '',
          workspace_name: window.HOROS_WORKSPACE?.workspace_name || '',
          avatarUrl, initials, color,
          ...planInfo(window.HOROS_WORKSPACE),
          created_at: u.created_at,
        });
      } catch (e) { console.error('[perfil]', e); }
      setLoading(false);
    };
    init();
  }, []);

  const startEdit = () => {
    setEditForm({ nome: profile.nome, sobrenome: profile.sobrenome, cargo: profile.cargo });
    setPendingAvatar(null);
    setEditing(true);
  };

  const cancelEdit = () => { setEditing(false); setEditForm({}); setPendingAvatar(null); };

  const dirty = editing && (
    pendingAvatar !== null ||
    editForm.nome !== profile.nome || editForm.sobrenome !== profile.sobrenome ||
    editForm.cargo !== profile.cargo
  );

  const uploadAvatar = async (dataUrl, userId) => {
    if (dataUrl) {
      const res = await fetch(dataUrl);
      const blob = await res.blob();
      const ext = 'jpg'; // canvas always outputs jpeg
      const path = `${userId}/avatar.${ext}`;
      await window.HOROS_SB.storage.from('avatars').remove([
        `${userId}/avatar.jpg`, `${userId}/avatar.png`, `${userId}/avatar.webp`,
      ]);
      const { error } = await window.HOROS_SB.storage
        .from('avatars').upload(path, blob, { upsert: true, contentType: 'image/jpeg' });
      if (error) throw error;
      const { data: { publicUrl } } = window.HOROS_SB.storage.from('avatars').getPublicUrl(path);
      const url = `${publicUrl}?t=${Date.now()}`;
      await window.HOROS_SB.from('profiles').update({ avatar_url: url }).eq('id', userId);
      localStorage.setItem(pfAvatarKey(), url);
      return url;
    } else {
      await window.HOROS_SB.storage.from('avatars').remove([
        `${userId}/avatar.jpg`, `${userId}/avatar.png`, `${userId}/avatar.webp`,
      ]);
      await window.HOROS_SB.from('profiles').update({ avatar_url: null }).eq('id', userId);
      localStorage.removeItem(pfAvatarKey());
      return '';
    }
  };

  const saveHeader = async () => {
    setSaving(true);
    try {
      const userId = window.HOROS_USER?.id;
      const fieldsChanged = editForm.nome !== profile.nome ||
        editForm.sobrenome !== profile.sobrenome || editForm.cargo !== profile.cargo;

      if (pendingAvatar !== null) {
        const newUrl = await uploadAvatar(pendingAvatar, userId);
        setProfile(p => ({ ...p, avatarUrl: newUrl }));
      }

      if (fieldsChanged) {
        await window.HOROS_SB.from('profiles').update({
          nome: editForm.nome,
          sobrenome: editForm.sobrenome,
          cargo: editForm.cargo,
        }).eq('id', userId);
        const n = editForm.nome || '';
        const s = editForm.sobrenome || '';
        const initials = ((n && s) ? n[0]+s[0] : n[0] || '?').toUpperCase();
        setProfile(p => ({ ...p, ...editForm, initials }));
      }

      setPendingAvatar(null);
      setEditing(false);
      setEditForm({});
      toast('Perfil atualizado');
    } catch (e) {
      console.error('[saveHeader]', e);
      toast('Erro ao salvar perfil', 'error');
    }
    setSaving(false);
  };

  const onAvatarChange = (dataUrl) => setPendingAvatar(dataUrl);

  const updateProfile = (fields) => {
    const n = fields.nome || profile.nome;
    const s = fields.sobrenome || profile.sobrenome;
    const initials = ((n && s) ? n[0]+s[0] : n[0] || '?').toUpperCase();
    setProfile(p => ({ ...p, ...fields, initials }));
  };

  if (loading) return (
    <div role="status" aria-label="Carregando perfil" style={{ padding: 24, display: 'flex', flexDirection: 'column', gap: 20 }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
        <Skel circle h={72} />
        <div style={{ flex: 1, display: 'flex', flexDirection: 'column', gap: 9 }}><Skel h={20} w="min(260px,60%)" /><Skel h={11} w="min(200px,45%)" /></div>
      </div>
      <div style={{ display: 'flex', gap: 18 }}><Skel w={70} h={12} /><Skel w={90} h={12} /><Skel w={80} h={12} /><Skel w={90} h={12} /></div>
      <div className="hx-sk-card" style={{ gap: 18 }}>
        {[0, 1, 2].map(i => <div key={i} style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 16 }}><SkelText lines={2} last="80%" /><SkelText lines={2} last="70%" /></div>)}
      </div>
    </div>
  );

  return (
    <div className="pf-page">
      <ProfileHeader
        profile={profile}
        editing={editing}
        editForm={editForm}
        onEditChange={(k, v) => setEditForm(f => ({ ...f, [k]: v }))}
        onEdit={startEdit}
        onSave={saveHeader}
        onCancel={cancelEdit}
        saving={saving}
        dirty={dirty}
        onAvatarChange={onAvatarChange}
        effectiveAvatarUrl={pendingAvatar !== null ? pendingAvatar : profile.avatarUrl}
      />

      <nav className="pf-tabs-nav" role="tablist" aria-label="Seções do perfil">
        {TABS.map(tab => (
          <button
            key={tab.id}
            className={`pf-tab-btn${activeTab === tab.id ? ' active' : ''}${tab.danger ? ' pf-tab-btn--danger' : ''}`}
            role="tab"
            aria-selected={activeTab === tab.id}
            onClick={() => setActiveTab(tab.id)}
          >
            <Icon name={tab.icon} size={13} />
            {tab.label}
          </button>
        ))}
      </nav>

      <div className="pf-tab-pane" role="tabpanel">
        {blockedParam && canBill === false && (
          <div className="pf-bill-alert" role="alert" style={{ marginTop: 0, marginBottom: 16 }}>
            <b>A assinatura da sua {segTerm('company', 'imobiliária')} não está ativa.</b>{' '}
            Peça para quem cuida dos pagamentos da {segTerm('company', 'imobiliária')} escolher um plano para liberar o acesso.
          </div>
        )}
        {activeTab === 'perfil' && (
          <DadosPessoaisCard profile={profile} onUpdate={updateProfile} toast={toast} />
        )}
        {activeTab === 'seguranca' && (
          <>
            <SegurancaCard toast={toast} />
            <SessoesCard toast={toast} />
            <DoisFatoresCard toast={toast} />
          </>
        )}
        {activeTab === 'preferencias' && (
          <PreferenciasCard theme={theme} onThemeChange={onThemeChange} toast={toast} />
        )}
        {activeTab === 'notificacoes' && (
          <NotificacoesCard />
        )}
        {activeTab === 'ia' && (
          <IACard toast={toast} />
        )}
        {activeTab === 'assinatura' && canBill === true && (
          <AssinaturaCard toast={toast} profile={profile} />
        )}
        {activeTab === 'dados' && (
          <PrivacidadeDadosCard toast={toast} />
        )}
        {activeTab === 'perigo' && (
          <ZonaDePerigoCard toast={toast} />
        )}
      </div>

      <ToastStack toasts={toasts} />
    </div>
  );
}

window.PerfilPage = PerfilPage;
