/* admin_users_modals.jsx — 3 fluxos de gestão acima da tabela:
   - NovoUtilizadorModal     · wizard 3-step (Identidade · Vínculo · Acesso)
   - ImportCSVModal          · 4-step (Upload · Mapeamento · Validação · Confirmação)
   - SyncGraphPanel          · popover ancorado ao botão (estado + drift list)

   Expostos em window.AdminUsersModals.
*/
const D  = window.AdminUsersData;
const UI = window.AdminUsers;

// ════════════════════════════════════════════════════════════════
// SHELL: ModalShell + Wizard helpers
// ════════════════════════════════════════════════════════════════
const ModalShell = ({ children, onClose, width = 640, title, subtitle, icon }) => {
  React.useEffect(() => {
    const h = (e) => { if (e.key === 'Escape') onClose(); };
    document.addEventListener('keydown', h);
    return () => document.removeEventListener('keydown', h);
  }, [onClose]);
  return (
    <>
      <div onClick={onClose} style={{
        position: 'fixed', inset: 0, zIndex: 1400,
        background: 'rgba(0,0,0,0.5)', backdropFilter: 'blur(3px)',
        animation: 'fadeIn 140ms ease-out',
      }} />
      <div role="dialog" aria-modal="true" style={{
        position: 'fixed', top: '50%', left: '50%', zIndex: 1401,
        transform: 'translate(-50%,-50%)',
        width, maxWidth: '94vw', maxHeight: '90vh',
        background: 'var(--bg)', border: '1px solid var(--border)',
        borderRadius: 12, boxShadow: '0 24px 64px rgba(0,0,0,0.32)',
        display: 'flex', flexDirection: 'column',
        animation: 'modalIn 200ms cubic-bezier(0.4,0,0.2,1)',
        fontFamily: 'var(--font-sans)',
      }}>
        {(title || icon) && (
          <header style={{
            padding: '16px 20px', borderBottom: '1px solid var(--border)',
            display: 'flex', alignItems: 'center', gap: 12, flexShrink: 0,
          }}>
            {icon && (
              <div style={{
                width: 36, height: 36, borderRadius: 8,
                background: 'color-mix(in oklch, var(--ai-500) 14%, transparent)',
                color: 'var(--ai-500)',
                display: 'grid', placeItems: 'center',
              }}>
                <Icon name={icon} size={16} />
              </div>
            )}
            <div style={{ flex: 1 }}>
              <h2 className="font-display" style={{ margin: 0, fontSize: 16, fontWeight: 600 }}>{title}</h2>
              {subtitle && <div style={{ fontSize: 11.5, color: 'var(--text-muted)', marginTop: 2 }}>{subtitle}</div>}
            </div>
            <button onClick={onClose} className="btn btn-sm btn-ghost"><Icon name="close" size={13} /></button>
          </header>
        )}
        {children}
      </div>
    </>
  );
};

const StepIndicator = ({ steps, current }) => (
  <div style={{
    display: 'flex', alignItems: 'center', gap: 0,
    padding: '14px 20px',
    background: 'var(--bg-elev)',
    borderBottom: '1px solid var(--border)',
  }}>
    {steps.map((s, i) => {
      const done = i < current;
      const active = i === current;
      return (
        <React.Fragment key={s.id}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 8, flex: 'none' }}>
            <div style={{
              width: 22, height: 22, borderRadius: '50%',
              display: 'grid', placeItems: 'center',
              fontSize: 11, fontWeight: 700, fontFamily: 'var(--font-mono)',
              background: done ? 'var(--ok)' : active ? 'var(--ai-500)' : 'var(--bg-sunken)',
              color: done || active ? '#fff' : 'var(--text-dim)',
              border: active ? '0' : `1px solid ${done ? 'var(--ok)' : 'var(--border)'}`,
              transition: 'all 160ms',
            }}>{done ? <Icon name="check" size={11} /> : i + 1}</div>
            <span style={{
              fontSize: 11.5,
              fontWeight: active ? 600 : 500,
              color: active ? 'var(--text)' : done ? 'var(--text-muted)' : 'var(--text-dim)',
            }}>{s.label}</span>
          </div>
          {i < steps.length - 1 && (
            <div style={{
              flex: 1, height: 1, margin: '0 12px',
              background: done ? 'var(--ok)' : 'var(--border)',
              transition: 'background 160ms',
            }} />
          )}
        </React.Fragment>
      );
    })}
  </div>
);

const WizardFooter = ({ canPrev, canNext, isLast, onPrev, onNext, onSubmit, onCancel, submitLabel = 'Criar', primaryDisabled }) => (
  <footer style={{
    padding: '12px 20px', borderTop: '1px solid var(--border)',
    background: 'var(--bg-elev)',
    display: 'flex', alignItems: 'center', gap: 8, flexShrink: 0,
  }}>
    <button onClick={onCancel} className="btn btn-sm btn-ghost">Cancelar</button>
    <div style={{ flex: 1 }} />
    {canPrev && <button onClick={onPrev} className="btn btn-sm"><Icon name="arrowUp" size={11} style={{ transform: 'rotate(-90deg)' }} />Voltar</button>}
    {!isLast && (
      <button onClick={onNext} className="btn btn-sm btn-primary" disabled={!canNext}>
        Próximo<Icon name="arrowUp" size={11} style={{ transform: 'rotate(90deg)' }} />
      </button>
    )}
    {isLast && (
      <button onClick={onSubmit} className="btn btn-sm btn-primary" disabled={primaryDisabled}>
        <Icon name="check" size={12} />{submitLabel}
      </button>
    )}
  </footer>
);

// Tiny form primitives ────────────────────────────────────────
const Field = ({ label, hint, error, children, required }) => (
  <label style={{ display: 'block', marginBottom: 14 }}>
    <div style={{
      fontSize: 10.5, fontFamily: 'var(--font-display)', fontWeight: 600,
      letterSpacing: '0.10em', textTransform: 'uppercase',
      color: error ? 'var(--err)' : 'var(--text-muted)', marginBottom: 5,
    }}>
      {label}
      {required && <span style={{ color: 'var(--err)', marginLeft: 3 }}>*</span>}
    </div>
    {children}
    {hint && !error && <div style={{ fontSize: 10.5, color: 'var(--text-dim)', marginTop: 4 }}>{hint}</div>}
    {error && <div style={{ fontSize: 10.5, color: 'var(--err)', marginTop: 4, display: 'flex', alignItems: 'center', gap: 4 }}><Icon name="warning" size={10} />{error}</div>}
  </label>
);

const inputStyle = {
  width: '100%', padding: '8px 10px',
  background: 'var(--bg-elev)', border: '1px solid var(--border)',
  borderRadius: 6, fontSize: 13, color: 'var(--text)',
  fontFamily: 'var(--font-sans)', outline: 'none',
  transition: 'border-color 100ms',
};
const TextInput = (p) => <input style={inputStyle} {...p} />;
const SelectInput = ({ options, ...p }) => (
  <select style={inputStyle} {...p}>
    {options.map(o => typeof o === 'string'
      ? <option key={o} value={o}>{o}</option>
      : <option key={o.id} value={o.id}>{o.label}</option>)}
  </select>
);
const Toggle = ({ checked, onChange, label, sub }) => (
  <label style={{
    display: 'flex', alignItems: 'flex-start', gap: 10,
    padding: '10px 12px', borderRadius: 6,
    border: '1px solid var(--border)', background: 'var(--bg-elev)',
    cursor: 'pointer', marginBottom: 8,
  }}>
    <span style={{
      flexShrink: 0, marginTop: 2,
      width: 32, height: 18, borderRadius: 999,
      background: checked ? 'var(--ai-500)' : 'var(--bg-sunken)',
      border: '1px solid ' + (checked ? 'var(--ai-500)' : 'var(--border)'),
      position: 'relative', transition: 'all 140ms',
    }}>
      <span style={{
        position: 'absolute', top: 1, left: checked ? 14 : 1,
        width: 14, height: 14, borderRadius: '50%',
        background: '#fff', transition: 'left 140ms',
      }} />
    </span>
    <input type="checkbox" checked={checked} onChange={e => onChange(e.target.checked)} style={{ display: 'none' }} />
    <div style={{ flex: 1 }}>
      <div style={{ fontSize: 12.5, fontWeight: 500 }}>{label}</div>
      {sub && <div style={{ fontSize: 11, color: 'var(--text-muted)', marginTop: 2 }}>{sub}</div>}
    </div>
  </label>
);

// ════════════════════════════════════════════════════════════════
// 1 · NovoUtilizadorModal
// ════════════════════════════════════════════════════════════════
const NovoUtilizadorModal = ({ onClose, onCreate }) => {
  const [step, setStep] = React.useState(0);
  const [data, setData] = React.useState({
    // step 1
    nome_completo: '', email_profissional: '', telemovel: '',
    // step 2
    empresa_primaria: 'DDI', cargo: '', departamento: 'Comercial',
    tipo_utilizador: 'comercial', seniority: 'senior',
    // step 3
    portal_activo: true, send_welcome: true, apollo_auto: true,
    notas_admin: '',
  });
  const [submitting, setSubmitting] = React.useState(false);
  const upd = (patch) => setData(d => ({ ...d, ...patch }));

  const errors = React.useMemo(() => {
    const e = {};
    if (step === 0) {
      if (!data.nome_completo.trim()) e.nome_completo = 'Obrigatório';
      else if (data.nome_completo.trim().split(/\s+/).length < 2) e.nome_completo = 'Nome e apelido';
      if (!data.email_profissional.trim()) e.email = 'Obrigatório';
      else if (!/@digidelta\.(pt|es)$/i.test(data.email_profissional)) e.email = 'Tem de terminar em @digidelta.pt ou @digidelta.es';
      const dup = D.ALL_USERS.find(u => u.email_profissional?.toLowerCase() === data.email_profissional.toLowerCase());
      if (dup) e.email = `Já existe (${dup.nome_apresentar})`;
    }
    if (step === 1) {
      if (!data.cargo.trim()) e.cargo = 'Obrigatório';
    }
    return e;
  }, [step, data]);
  const canNext = Object.keys(errors).length === 0;

  // auto-derivar email a partir do nome
  React.useEffect(() => {
    if (step !== 0 || !data.nome_completo || data.email_profissional) return;
    const parts = data.nome_completo.trim().toLowerCase().split(/\s+/).filter(Boolean);
    if (parts.length >= 2) {
      const candidate = `${parts[0]}.${parts[parts.length - 1]}@digidelta.pt`
        .normalize('NFD').replace(/[\u0300-\u036f]/g, '');
      upd({ email_profissional: candidate });
    }
  }, [data.nome_completo]);

  const submit = () => {
    setSubmitting(true);
    setTimeout(() => {
      onCreate?.({ ...data, id: D.ALL_USERS.length + 1, criado_em: new Date().toISOString().slice(0,10) });
      setSubmitting(false);
      onClose();
    }, 800);
  };

  const steps = [
    { id: 'identidade', label: 'Identidade' },
    { id: 'vinculo',    label: 'Vínculo' },
    { id: 'acesso',     label: 'Acesso' },
  ];

  return (
    <ModalShell width={640} onClose={onClose} icon="plus" title="Novo utilizador" subtitle="Adicionar um colaborador ao Grupo Farben Consulting">
      <StepIndicator steps={steps} current={step} />

      <div className="scrollbar" style={{ flex: 1, overflowY: 'auto', padding: '20px 24px', minHeight: 280 }}>
        {step === 0 && (
          <>
            <Field label="Nome completo" required error={errors.nome_completo} hint="Ex: Maria Silva — duas palavras mínimo">
              <TextInput
                value={data.nome_completo}
                onChange={e => upd({ nome_completo: e.target.value })}
                placeholder="Maria Silva"
                autoFocus
              />
            </Field>
            <Field label="Email profissional" required error={errors.email} hint="Auto-preenchido a partir do nome — domain @digidelta.pt | .es">
              <TextInput
                value={data.email_profissional}
                onChange={e => upd({ email_profissional: e.target.value })}
                placeholder="maria.silva@digidelta.pt"
                type="email"
              />
            </Field>
            <Field label="Telemóvel" hint="Opcional · formato +351 9XX XXX XXX">
              <TextInput
                value={data.telemovel}
                onChange={e => upd({ telemovel: e.target.value })}
                placeholder="+351 912 345 678"
              />
            </Field>
            <div style={{
              padding: '10px 12px', background: 'var(--bg-sunken)', borderRadius: 6,
              fontSize: 11.5, color: 'var(--text-muted)',
              display: 'flex', alignItems: 'flex-start', gap: 8,
            }}>
              <Icon name="circle" size={11} style={{ color: 'var(--text-dim)', marginTop: 2 }} />
              <span>Foto, idioma e LinkedIn podem ser adicionados depois no drawer do utilizador. O Microsoft Graph traz nome, email e foto na próxima sync.</span>
            </div>
          </>
        )}

        {step === 1 && (
          <>
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
              <Field label="Empresa primária" required hint="Pode adicionar mais empresas depois">
                <SelectInput
                  value={data.empresa_primaria}
                  onChange={e => upd({ empresa_primaria: e.target.value })}
                  options={D.EMPRESAS.map(e => ({ id: e.id, label: `${e.short} — ${e.nome}` }))}
                />
              </Field>
              <Field label="Departamento">
                <SelectInput
                  value={data.departamento}
                  onChange={e => upd({ departamento: e.target.value })}
                  options={['Comercial','Marketing','Técnico','Financeiro','Operações','Administração','C-Suite']}
                />
              </Field>
            </div>
            <Field label="Cargo" required error={errors.cargo} hint="Texto livre — ex: Sales Executive, Marketing Manager">
              <TextInput
                value={data.cargo}
                onChange={e => upd({ cargo: e.target.value })}
                placeholder="Sales Executive"
              />
            </Field>
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
              <Field label="Tipo de utilizador" required hint="Define permissões iniciais (RBAC)">
                <SelectInput
                  value={data.tipo_utilizador}
                  onChange={e => upd({ tipo_utilizador: e.target.value })}
                  options={D.TIPOS.map(t => ({ id: t.id, label: t.label }))}
                />
              </Field>
              <Field label="Seniority">
                <SelectInput
                  value={data.seniority}
                  onChange={e => upd({ seniority: e.target.value })}
                  options={D.SENIORITIES}
                />
              </Field>
            </div>
            <div style={{
              padding: '10px 12px',
              background: 'color-mix(in oklch, var(--ai-500) 6%, transparent)',
              border: '1px solid color-mix(in oklch, var(--ai-500) 18%, transparent)',
              borderRadius: 6, fontSize: 11.5, color: 'var(--text-muted)',
              display: 'flex', alignItems: 'flex-start', gap: 8,
            }}>
              <Icon name="sparkle" size={11} style={{ color: 'var(--ai-500)', marginTop: 2 }} />
              <span>Vais herdar as permissões default de <strong style={{ color: 'var(--text)' }}>{D.getTipo(data.tipo_utilizador)?.label}</strong> — podes ajustar individualmente no drawer.</span>
            </div>
          </>
        )}

        {step === 2 && (
          <>
            <Toggle
              checked={data.portal_activo}
              onChange={(v) => upd({ portal_activo: v })}
              label="Criar acesso ao Portal Digi"
              sub="Username = email · password gerada · SSO Microsoft 365"
            />
            <Toggle
              checked={data.send_welcome}
              onChange={(v) => upd({ send_welcome: v })}
              label="Enviar email de boas-vindas com magic link"
              sub="Magic link válido por 24h. Utilizador define password e 2FA no primeiro acesso."
            />
            <Toggle
              checked={data.apollo_auto}
              onChange={(v) => upd({ apollo_auto: v })}
              label="Enriquecer via Apollo automaticamente"
              sub="Vai buscar LinkedIn, áreas de expertise e perfil profissional. Custa ~1 crédito Apollo."
            />
            <Field label="Notas admin (interno)" hint="Visível apenas a Admin e Director · não aparece no perfil público">
              <textarea
                style={{ ...inputStyle, minHeight: 60, resize: 'vertical', fontFamily: 'var(--font-sans)' }}
                value={data.notas_admin}
                onChange={e => upd({ notas_admin: e.target.value })}
                placeholder="Ex: contratado em substituição da Joana, comeca dia 5 Maio…"
              />
            </Field>

            {/* Summary card */}
            <div style={{
              marginTop: 6, padding: '12px 14px',
              background: 'var(--bg-elev)', border: '1px solid var(--border)',
              borderRadius: 8,
            }}>
              <div className="font-display" style={{
                fontSize: 9.5, fontWeight: 600, letterSpacing: '0.10em',
                textTransform: 'uppercase', color: 'var(--text-muted)', marginBottom: 8,
              }}>Resumo</div>
              <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 8 }}>
                <div style={{
                  width: 32, height: 32, borderRadius: '50%',
                  background: 'var(--brand-700, var(--ai-500))', color: '#fff',
                  display: 'grid', placeItems: 'center',
                  fontWeight: 600, fontSize: 11, fontFamily: 'var(--font-display)',
                }}>{(data.nome_completo || 'NU').split(' ').map(n => n[0]).slice(0,2).join('').toUpperCase()}</div>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ fontSize: 13, fontWeight: 500 }}>{data.nome_completo || '—'}</div>
                  <div style={{ fontSize: 11, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)' }}>{data.email_profissional || '—'}</div>
                </div>
                {UI && <UI.TipoBadge tipo={data.tipo_utilizador} />}
              </div>
              <div style={{ fontSize: 11.5, color: 'var(--text-muted)', lineHeight: 1.6 }}>
                <div>{data.cargo} · {D.getEmpresa(data.empresa_primaria)?.nome} · {data.departamento}</div>
                <div style={{ marginTop: 6, display: 'flex', flexWrap: 'wrap', gap: 6 }}>
                  {data.portal_activo && <SummaryChip>Portal</SummaryChip>}
                  {data.send_welcome   && <SummaryChip>Magic link</SummaryChip>}
                  {data.apollo_auto    && <SummaryChip>Apollo</SummaryChip>}
                </div>
              </div>
            </div>
          </>
        )}
      </div>

      <WizardFooter
        canPrev={step > 0}
        canNext={canNext}
        isLast={step === 2}
        onPrev={() => setStep(s => s - 1)}
        onNext={() => setStep(s => s + 1)}
        onSubmit={submit}
        onCancel={onClose}
        submitLabel={submitting ? 'A criar…' : 'Criar utilizador'}
        primaryDisabled={submitting}
      />
    </ModalShell>
  );
};

const SummaryChip = ({ children }) => (
  <span style={{
    fontSize: 10, padding: '2px 8px', borderRadius: 999,
    background: 'color-mix(in oklch, var(--ok) 14%, transparent)',
    color: 'var(--ok)', fontFamily: 'var(--font-mono)', fontWeight: 600, letterSpacing: '0.04em',
  }}>{children}</span>
);

// ════════════════════════════════════════════════════════════════
// 2 · ImportCSVModal
// ════════════════════════════════════════════════════════════════
const CSV_FIELDS = [
  { id: 'nome_completo',     label: 'Nome completo',  required: true,  example: 'Maria Silva' },
  { id: 'email',             label: 'Email',          required: true,  example: 'maria.silva@digidelta.pt' },
  { id: 'telemovel',         label: 'Telemóvel',      required: false, example: '+351 912 345 678' },
  { id: 'empresa_codigo',    label: 'Empresa (código)', required: true, example: 'DDI' },
  { id: 'cargo',             label: 'Cargo',          required: true,  example: 'Sales Executive' },
  { id: 'departamento',      label: 'Departamento',   required: false, example: 'Comercial' },
  { id: 'tipo_utilizador',   label: 'Tipo',           required: true,  example: 'comercial' },
  { id: 'seniority',         label: 'Seniority',      required: false, example: 'senior' },
  { id: 'pais',              label: 'País',           required: false, example: 'Portugal' },
  { id: 'localidade',        label: 'Localidade',     required: false, example: 'Lisboa' },
  { id: 'office',            label: 'Office',         required: false, example: 'Lisboa HQ' },
  { id: 'idioma_preferido',  label: 'Idioma',         required: false, example: 'PT' },
];

// Mock dataset preview (51 linhas)
const MOCK_PREVIEW = [
  { nome: 'Carla Ribeiro', email: 'carla.ribeiro@digidelta.pt', empresa: 'DDI', cargo: 'Account Manager', tipo: 'comercial', status: 'ok' },
  { nome: 'Hugo Pereira', email: 'hugo.pereira@digidelta.pt', empresa: 'FBS', cargo: 'Marketing Specialist', tipo: 'marketing', status: 'ok' },
  { nome: 'Sofia Mendes', email: 'sofia.mendes@digidelta.pt', empresa: 'DGT', cargo: 'Sales Rep', tipo: 'comercial', status: 'warn', warn: 'Empresa "DGT" não existe — sugerido: DDI' },
  { nome: 'Ricardo Faria', email: 'ricardo.faria@digidelta.pt', empresa: 'DDI', cargo: 'Engineer', tipo: 'tecnico', status: 'ok' },
  { nome: 'Ana Costa', email: 'fabio.costa@digidelta.pt', empresa: 'FBS', cargo: 'Manager', tipo: 'manager', status: 'err', err: 'Email já existe (Fábio Costa)' },
  { nome: 'Pedro Lima', email: 'pedro.lima@digidelta.pt', empresa: 'NSC', cargo: 'Technical Support', tipo: 'tecnico', status: 'ok' },
  { nome: 'Inês Santos', email: 'ines.santos@digidelta.pt', empresa: 'DDI', cargo: 'Back-Office', tipo: 'administrativo', status: 'ok' },
  { nome: 'Miguel Alves', email: 'miguel@gmail.com', empresa: 'DDI', cargo: 'Sales', tipo: 'comercial', status: 'err', err: 'Domain inválido — esperado @digidelta.pt' },
  { nome: 'Joana Faria', email: 'joana.faria@digidelta.es', empresa: 'DDD', cargo: 'Sales Rep', tipo: 'comercial', status: 'ok' },
  { nome: 'Tiago Lopes', email: 'tiago.lopes@digidelta.pt', empresa: 'MWS', cargo: 'Operations', tipo: 'operacional', status: 'warn', warn: 'Departamento vazio — usar default "Operações"?' },
];

const ImportCSVModal = ({ onClose, onImport }) => {
  const [step, setStep] = React.useState(0);
  const [file, setFile] = React.useState(null);
  const [submitting, setSubmitting] = React.useState(false);
  const [progress, setProgress] = React.useState(0);
  const [dryRun, setDryRun] = React.useState(false);

  const okCount   = MOCK_PREVIEW.filter(r => r.status === 'ok').length;
  const warnCount = MOCK_PREVIEW.filter(r => r.status === 'warn').length;
  const errCount  = MOCK_PREVIEW.filter(r => r.status === 'err').length;

  const submit = () => {
    setSubmitting(true);
    let p = 0;
    const t = setInterval(() => {
      p += 8 + Math.random() * 14;
      setProgress(Math.min(p, 100));
      if (p >= 100) {
        clearInterval(t);
        setTimeout(() => {
          onImport?.({ created: okCount, warnings: warnCount, errors: errCount, dryRun });
          setSubmitting(false);
          onClose();
        }, 400);
      }
    }, 200);
  };

  const steps = [
    { id: 'upload',    label: 'Upload' },
    { id: 'mapping',   label: 'Mapeamento' },
    { id: 'preview',   label: 'Validação' },
    { id: 'confirm',   label: 'Confirmação' },
  ];

  return (
    <ModalShell width={880} onClose={onClose} icon="download" title="Importar utilizadores via CSV" subtitle="Bulk insert · ideal para onboarding inicial ou aquisições">
      <StepIndicator steps={steps} current={step} />

      <div className="scrollbar" style={{ flex: 1, overflowY: 'auto', padding: '20px 24px', minHeight: 360 }}>
        {step === 0 && <CSVStepUpload file={file} setFile={setFile} />}
        {step === 1 && <CSVStepMapping />}
        {step === 2 && <CSVStepPreview ok={okCount} warn={warnCount} err={errCount} />}
        {step === 3 && <CSVStepConfirm ok={okCount} warn={warnCount} err={errCount} dryRun={dryRun} setDryRun={setDryRun} submitting={submitting} progress={progress} />}
      </div>

      <WizardFooter
        canPrev={step > 0 && !submitting}
        canNext={step === 0 ? !!file : true}
        isLast={step === 3}
        onPrev={() => setStep(s => s - 1)}
        onNext={() => setStep(s => s + 1)}
        onSubmit={submit}
        onCancel={onClose}
        submitLabel={submitting ? `A importar… ${Math.round(progress)}%` : (dryRun ? 'Executar dry-run' : `Importar ${okCount} utilizadores`)}
        primaryDisabled={submitting || (errCount > 0 && !dryRun && step === 3)}
      />
    </ModalShell>
  );
};

const CSVStepUpload = ({ file, setFile }) => (
  <div>
    <div style={{
      border: '2px dashed var(--border)',
      borderRadius: 10, padding: '40px 24px',
      textAlign: 'center', cursor: 'pointer',
      background: file ? 'color-mix(in oklch, var(--ok) 6%, transparent)' : 'var(--bg-elev)',
      transition: 'all 140ms',
    }} onClick={() => setFile({ name: 'colaboradores_2026.csv', size: 12_400, rows: 51 })}>
      <Icon name={file ? 'check' : 'download'} size={28} style={{ color: file ? 'var(--ok)' : 'var(--text-muted)', marginBottom: 12 }} />
      <div style={{ fontSize: 14, fontWeight: 500, marginBottom: 4 }}>
        {file ? file.name : 'Arrasta um ficheiro CSV ou clica para escolher'}
      </div>
      <div style={{ fontSize: 11.5, color: 'var(--text-muted)' }}>
        {file
          ? `${(file.size/1024).toFixed(1)} KB · ${file.rows} linhas detectadas · pronto para mapeamento`
          : 'Formato UTF-8 · vírgula ou ponto-e-vírgula como separador · primeira linha = cabeçalhos'}
      </div>
    </div>

    <div style={{ marginTop: 16, display: 'flex', alignItems: 'center', gap: 10 }}>
      <button className="btn btn-sm" onClick={(e) => e.stopPropagation()}>
        <Icon name="download" size={11} />Descarregar template
      </button>
      <span style={{ fontSize: 11, color: 'var(--text-dim)' }}>
        Template com 12 colunas e 3 exemplos preenchidos.
      </span>
    </div>

    <details style={{ marginTop: 14, fontSize: 12 }}>
      <summary style={{ cursor: 'pointer', color: 'var(--text-muted)', padding: '4px 0' }}>
        <Icon name="info" size={11} style={{ marginRight: 5 }} />
        Ver colunas esperadas (12)
      </summary>
      <div style={{
        marginTop: 10, padding: 12,
        background: 'var(--bg-sunken)', borderRadius: 6,
        fontFamily: 'var(--font-mono)', fontSize: 11,
        color: 'var(--text-muted)', lineHeight: 1.7,
      }}>
        {CSV_FIELDS.map(f => (
          <div key={f.id} style={{ display: 'flex', gap: 12 }}>
            <span style={{ color: f.required ? 'var(--text)' : 'var(--text-dim)', minWidth: 140 }}>
              {f.id}{f.required && <span style={{ color: 'var(--err)' }}>*</span>}
            </span>
            <span style={{ color: 'var(--text-dim)' }}>{f.example}</span>
          </div>
        ))}
      </div>
    </details>
  </div>
);

const CSVStepMapping = () => {
  // simula auto-match com nomes do ficheiro
  const fileColumns = [
    { csv: 'nome',        match: 'nome_completo',   confidence: 'high' },
    { csv: 'mail',        match: 'email',           confidence: 'high' },
    { csv: 'telefone',    match: 'telemovel',       confidence: 'medium' },
    { csv: 'empresa',     match: 'empresa_codigo',  confidence: 'high' },
    { csv: 'role',        match: 'cargo',           confidence: 'medium' },
    { csv: 'tipo',        match: 'tipo_utilizador', confidence: 'high' },
    { csv: 'pais',        match: 'pais',            confidence: 'high' },
    { csv: 'cidade',      match: 'localidade',      confidence: 'high' },
    { csv: 'lang',        match: 'idioma_preferido', confidence: 'medium' },
    { csv: 'departamento', match: '',               confidence: 'none' },
    { csv: 'observacoes',  match: 'ignore',         confidence: 'high' },
  ];
  return (
    <div>
      <p style={{ fontSize: 12.5, color: 'var(--text-muted)', marginTop: 0, marginBottom: 14, lineHeight: 1.5 }}>
        Auto-match das colunas do CSV para campos da BD. <strong style={{ color: 'var(--text)' }}>9 de 11 colunas</strong> mapeadas automaticamente. Confirma ou ajusta o mapeamento abaixo.
      </p>
      <div style={{
        border: '1px solid var(--border)', borderRadius: 8, overflow: 'hidden',
        background: 'var(--bg-elev)',
      }}>
        <div style={{
          display: 'grid', gridTemplateColumns: '1fr 24px 1fr 70px',
          padding: '8px 14px', background: 'var(--bg-sunken)',
          borderBottom: '1px solid var(--border)',
          fontSize: 9.5, fontFamily: 'var(--font-display)', fontWeight: 600,
          letterSpacing: '0.10em', textTransform: 'uppercase',
          color: 'var(--text-muted)',
        }}>
          <span>Coluna do CSV</span><span></span><span>Campo BD</span><span style={{ textAlign: 'right' }}>Confiança</span>
        </div>
        {fileColumns.map((c, i) => (
          <div key={i} style={{
            display: 'grid', gridTemplateColumns: '1fr 24px 1fr 70px',
            alignItems: 'center', padding: '8px 14px',
            borderBottom: i < fileColumns.length - 1 ? '1px solid var(--border)' : 'none',
            fontSize: 12,
          }}>
            <span style={{ fontFamily: 'var(--font-mono)', color: 'var(--text)' }}>{c.csv}</span>
            <span style={{ color: 'var(--text-dim)', textAlign: 'center' }}>→</span>
            <select defaultValue={c.match} style={{
              ...inputStyle, padding: '4px 8px', fontSize: 11.5,
              background: 'var(--bg)',
            }}>
              <option value="">— ignorar —</option>
              <option value="ignore">⊘ Ignorar coluna</option>
              {CSV_FIELDS.map(f => <option key={f.id} value={f.id}>{f.label}</option>)}
            </select>
            <ConfidenceBadge level={c.confidence} />
          </div>
        ))}
      </div>
    </div>
  );
};

const ConfidenceBadge = ({ level }) => {
  const map = {
    high:   { c: 'var(--ok)',     l: 'Alta' },
    medium: { c: 'var(--warn)',   l: 'Média' },
    none:   { c: 'var(--err)',    l: 'Manual' },
  };
  const m = map[level] || { c: 'var(--text-dim)', l: '—' };
  return (
    <span style={{
      textAlign: 'right', fontSize: 10, fontWeight: 600,
      color: m.c, fontFamily: 'var(--font-mono)', letterSpacing: '0.04em',
    }}>{m.l}</span>
  );
};

const CSVStepPreview = ({ ok, warn, err }) => (
  <div>
    <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 8, marginBottom: 16 }}>
      <PreviewStat label="OK"        value={ok}    color="var(--ok)" />
      <PreviewStat label="Warnings"  value={warn}  color="var(--warn)" />
      <PreviewStat label="Erros"     value={err}   color="var(--err)" />
    </div>
    <div style={{ fontSize: 12, color: 'var(--text-muted)', marginBottom: 10 }}>
      Primeiras {MOCK_PREVIEW.length} linhas. Linhas com erro <strong style={{ color: 'var(--err)' }}>não serão importadas</strong>. Warnings têm sugestão automática que podes aceitar ou rejeitar.
    </div>
    <div style={{
      border: '1px solid var(--border)', borderRadius: 8, overflow: 'hidden',
    }}>
      <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 12 }}>
        <thead style={{ background: 'var(--bg-sunken)' }}>
          <tr style={{ borderBottom: '1px solid var(--border)' }}>
            <th style={previewTh}></th>
            <th style={previewTh}>#</th>
            <th style={previewTh}>Nome</th>
            <th style={previewTh}>Email</th>
            <th style={previewTh}>Empresa</th>
            <th style={previewTh}>Cargo</th>
            <th style={previewTh}>Tipo</th>
          </tr>
        </thead>
        <tbody>
          {MOCK_PREVIEW.map((r, i) => (
            <React.Fragment key={i}>
              <tr style={{
                borderBottom: r.status === 'err' || r.status === 'warn' ? 'none' : '1px solid var(--border)',
                background: r.status === 'err' ? 'color-mix(in oklch, var(--err) 5%, transparent)'
                          : r.status === 'warn' ? 'color-mix(in oklch, var(--warn) 5%, transparent)'
                          : 'transparent',
              }}>
                <td style={{ padding: '6px 12px', width: 24 }}>
                  <StatusDot s={r.status} />
                </td>
                <td style={previewTd}>{i + 1}</td>
                <td style={previewTd}>{r.nome}</td>
                <td style={{ ...previewTd, fontFamily: 'var(--font-mono)', fontSize: 11 }}>{r.email}</td>
                <td style={previewTd}><span style={{ fontFamily: 'var(--font-mono)', fontSize: 11 }}>{r.empresa}</span></td>
                <td style={previewTd}>{r.cargo}</td>
                <td style={previewTd}><span style={{ fontFamily: 'var(--font-mono)', fontSize: 10.5, color: 'var(--text-muted)' }}>{r.tipo}</span></td>
              </tr>
              {r.warn && (
                <tr><td colSpan={7} style={{ padding: '0 12px 6px 36px', fontSize: 11, color: 'var(--warn)', borderBottom: '1px solid var(--border)' }}>
                  <Icon name="warning" size={10} style={{ marginRight: 5 }} />{r.warn}
                </td></tr>
              )}
              {r.err && (
                <tr><td colSpan={7} style={{ padding: '0 12px 6px 36px', fontSize: 11, color: 'var(--err)', borderBottom: '1px solid var(--border)' }}>
                  <Icon name="warning" size={10} style={{ marginRight: 5 }} />{r.err}
                </td></tr>
              )}
            </React.Fragment>
          ))}
        </tbody>
      </table>
    </div>
  </div>
);

const previewTh = {
  textAlign: 'left', padding: '8px 12px',
  fontSize: 9.5, fontFamily: 'var(--font-display)', fontWeight: 600,
  letterSpacing: '0.10em', textTransform: 'uppercase', color: 'var(--text-muted)',
};
const previewTd = { padding: '8px 12px', color: 'var(--text)' };

const PreviewStat = ({ label, value, color }) => (
  <div style={{
    padding: 10, borderRadius: 6,
    background: `color-mix(in oklch, ${color} 8%, transparent)`,
    border: `1px solid color-mix(in oklch, ${color} 22%, transparent)`,
  }}>
    <div style={{ fontSize: 22, fontWeight: 600, fontFamily: 'var(--font-display)', color, lineHeight: 1 }}>{value}</div>
    <div style={{ fontSize: 10, fontFamily: 'var(--font-display)', fontWeight: 600, letterSpacing: '0.10em', textTransform: 'uppercase', color: 'var(--text-muted)', marginTop: 4 }}>{label}</div>
  </div>
);
const StatusDot = ({ s }) => {
  const m = { ok: 'var(--ok)', warn: 'var(--warn)', err: 'var(--err)' };
  return <span style={{ display: 'inline-block', width: 7, height: 7, borderRadius: '50%', background: m[s] }} />;
};

const CSVStepConfirm = ({ ok, warn, err, dryRun, setDryRun, submitting, progress }) => (
  <div>
    <div style={{
      padding: '16px 18px', borderRadius: 8,
      background: 'var(--bg-elev)', border: '1px solid var(--border)',
      marginBottom: 16,
    }}>
      <div className="font-display" style={{
        fontSize: 9.5, fontWeight: 600, letterSpacing: '0.10em',
        textTransform: 'uppercase', color: 'var(--text-muted)', marginBottom: 10,
      }}>O que vai acontecer</div>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 8, fontSize: 12.5 }}>
        <ConfirmLine icon="check" color="var(--ok)" text={<span><strong>{ok} utilizadores</strong> serão criados na tabela <code style={inlineCode}>colaboradores</code></span>} />
        <ConfirmLine icon="warning" color="var(--warn)" text={<span><strong>{warn} warnings</strong> com sugestões automáticas aceites</span>} />
        <ConfirmLine icon="warning" color="var(--err)" text={<span><strong>{err} linhas</strong> com erro — não serão importadas</span>} />
        <ConfirmLine icon="mail" color="var(--text-muted)" text={<span><strong>{ok}</strong> emails de boas-vindas com magic link</span>} />
        <ConfirmLine icon="sparkle" color="var(--ai-500)" text={<span><strong>{ok}</strong> jobs de enriquecimento Apollo agendados</span>} />
        <ConfirmLine icon="history" color="var(--text-muted)" text={<span>Audit log: <code style={inlineCode}>colaboradores_audit</code> · autor: tu</span>} />
      </div>
    </div>

    <Toggle
      checked={dryRun}
      onChange={setDryRun}
      label="Executar dry-run primeiro"
      sub="Simula a importação sem escrever na BD. Útil para validar o resultado antes do commit."
    />

    {submitting && (
      <div style={{ marginTop: 16 }}>
        <div style={{ fontSize: 11, color: 'var(--text-muted)', marginBottom: 6, fontFamily: 'var(--font-mono)' }}>
          {dryRun ? 'A simular…' : 'A importar…'} {Math.round(progress)}%
        </div>
        <div style={{ height: 6, background: 'var(--bg-sunken)', borderRadius: 3, overflow: 'hidden' }}>
          <div style={{ height: '100%', width: `${progress}%`, background: 'var(--ai-500)', transition: 'width 200ms' }} />
        </div>
      </div>
    )}
  </div>
);

const ConfirmLine = ({ icon, color, text }) => (
  <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
    <Icon name={icon} size={12} style={{ color, flexShrink: 0 }} />
    <span style={{ color: 'var(--text)' }}>{text}</span>
  </div>
);
const inlineCode = {
  fontFamily: 'var(--font-mono)', fontSize: 10.5,
  padding: '1px 5px', background: 'var(--bg-sunken)',
  borderRadius: 3, color: 'var(--text-muted)',
};

// ════════════════════════════════════════════════════════════════
// 3 · SyncGraphPanel (popover)
// ════════════════════════════════════════════════════════════════
const MOCK_DRIFT = [
  { id: 1, type: 'email-changed', user: 'José Silva',     from: 'jose.s@digidelta.pt',     to: 'jose.silva@digidelta.pt', detected: '12min ago' },
  { id: 2, type: 'name-changed',  user: 'Catarina Sousa', from: 'Catarina S. Mendes',     to: 'Catarina Sousa',           detected: '12min ago' },
  { id: 3, type: 'photo-changed', user: 'Diogo Ferreira', from: '—',                       to: 'Nova foto disponível',     detected: '12min ago' },
  { id: 4, type: 'left-ad',       user: 'Mariana Brito',  from: 'Activo no AD',           to: 'Removida do AD',           detected: '1h ago' },
];

const SyncGraphPanel = ({ anchorRef, onClose }) => {
  const [pos, setPos] = React.useState({ top: 60, right: 28 });
  const [autoSync, setAutoSync] = React.useState(true);
  const [syncing, setSyncing] = React.useState(false);
  const [drift, setDrift] = React.useState(MOCK_DRIFT);
  const [appliedIds, setAppliedIds] = React.useState(new Set());

  // posicionar relativo ao anchor
  React.useEffect(() => {
    if (anchorRef?.current) {
      const r = anchorRef.current.getBoundingClientRect();
      setPos({ top: r.bottom + 6, right: window.innerWidth - r.right });
    }
  }, [anchorRef]);

  // close on outside click
  React.useEffect(() => {
    const h = (e) => { if (!e.target.closest('[data-sync-popover]') && !anchorRef?.current?.contains(e.target)) onClose(); };
    setTimeout(() => document.addEventListener('mousedown', h), 0);
    return () => document.removeEventListener('mousedown', h);
  }, [onClose, anchorRef]);

  const triggerSync = () => {
    setSyncing(true);
    setTimeout(() => { setSyncing(false); setDrift([]); setAppliedIds(new Set()); }, 1800);
  };

  const apply = (id) => setAppliedIds(s => new Set([...s, id]));
  const visibleDrift = drift.filter(d => !appliedIds.has(d.id));
  const status = syncing ? 'syncing' : visibleDrift.length === 0 ? 'in-sync' : 'drift';

  return (
    <div data-sync-popover style={{
      position: 'fixed', top: pos.top, right: pos.right, zIndex: 1350,
      width: 380, background: 'var(--bg-elev)',
      border: '1px solid var(--border)', borderRadius: 10,
      boxShadow: '0 16px 40px rgba(0,0,0,0.32)',
      animation: 'popIn 160ms cubic-bezier(0.4,0,0.2,1)',
      fontFamily: 'var(--font-sans)',
      overflow: 'hidden',
    }}>
      {/* Header */}
      <div style={{ padding: '14px 16px', borderBottom: '1px solid var(--border)' }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 4 }}>
          <Icon name="refresh" size={13} style={{ color: 'var(--ai-500)' }} />
          <h3 className="font-display" style={{ margin: 0, fontSize: 13, fontWeight: 600 }}>Microsoft Graph · Sync</h3>
          <div style={{ flex: 1 }} />
          <button onClick={onClose} className="btn btn-sm btn-ghost" style={{ padding: '2px 4px' }}>
            <Icon name="close" size={11} />
          </button>
        </div>
        <SyncStatus status={status} />
      </div>

      {/* Stats */}
      <div style={{
        display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)',
        gap: 1, background: 'var(--border)',
        borderBottom: '1px solid var(--border)',
      }}>
        <SyncStat label="Última sync" value="há 12min" />
        <SyncStat label="Utilizadores" value="187" />
        <SyncStat label="Drift" value={visibleDrift.length} accent={visibleDrift.length > 0 ? 'var(--warn)' : 'var(--ok)'} />
      </div>

      {/* Drift list */}
      <div className="scrollbar" style={{ maxHeight: 260, overflowY: 'auto' }}>
        {visibleDrift.length === 0 && !syncing && (
          <div style={{ padding: '24px 16px', textAlign: 'center', color: 'var(--text-muted)' }}>
            <Icon name="check" size={20} style={{ color: 'var(--ok)', marginBottom: 8 }} />
            <div style={{ fontSize: 12.5 }}>Tudo sincronizado.</div>
            <div style={{ fontSize: 11, color: 'var(--text-dim)', marginTop: 3 }}>
              {autoSync ? 'Próxima sync auto em 48min' : 'Auto-sync desactivado'}
            </div>
          </div>
        )}
        {syncing && (
          <div style={{ padding: '24px 16px', textAlign: 'center', color: 'var(--text-muted)' }}>
            <Icon name="refresh" size={18} style={{ color: 'var(--ai-500)', marginBottom: 8, animation: 'spin 1s linear infinite' }} />
            <div style={{ fontSize: 12.5 }}>A sincronizar…</div>
            <div style={{ fontSize: 10.5, color: 'var(--text-dim)', marginTop: 3, fontFamily: 'var(--font-mono)' }}>
              GET /v1.0/users?$select=…
            </div>
          </div>
        )}
        {!syncing && visibleDrift.map(d => <DriftItem key={d.id} drift={d} onApply={() => apply(d.id)} />)}
      </div>

      {/* Footer */}
      <div style={{
        padding: '10px 14px', borderTop: '1px solid var(--border)',
        background: 'var(--bg-sunken)',
      }}>
        <label style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 11.5, color: 'var(--text-muted)', cursor: 'pointer', marginBottom: 8 }}>
          <input
            type="checkbox" checked={autoSync} onChange={e => setAutoSync(e.target.checked)}
            style={{ accentColor: 'var(--ai-500)' }}
          />
          Auto-sync a cada 60 minutos
        </label>
        <div style={{ display: 'flex', gap: 6 }}>
          <button className="btn btn-sm btn-primary" onClick={triggerSync} disabled={syncing} style={{ flex: 1 }}>
            <Icon name="refresh" size={11} />
            {syncing ? 'A sincronizar…' : 'Sync agora'}
          </button>
          <button className="btn btn-sm btn-ghost" title="Configurar campos a sincronizar"><Icon name="filter" size={11} /></button>
          <button className="btn btn-sm btn-ghost" title="Ver log completo"><Icon name="history" size={11} /></button>
        </div>
      </div>
    </div>
  );
};

const SyncStatus = ({ status }) => {
  const m = {
    'in-sync': { c: 'var(--ok)',   l: 'Em sync · 187 utilizadores' },
    'drift':   { c: 'var(--warn)', l: 'Drift detectado · revisão necessária' },
    'syncing': { c: 'var(--ai-500)', l: 'A sincronizar…' },
    'error':   { c: 'var(--err)',  l: 'Erro · ver log' },
  }[status] || {};
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 11, color: m.c, fontFamily: 'var(--font-mono)', letterSpacing: '0.04em' }}>
      <span style={{
        width: 6, height: 6, borderRadius: '50%', background: m.c,
        boxShadow: `0 0 8px ${m.c}`,
        animation: status === 'syncing' ? 'pulse 1.2s ease-in-out infinite' : 'none',
      }} />
      {m.l}
    </div>
  );
};

const SyncStat = ({ label, value, accent }) => (
  <div style={{ background: 'var(--bg-elev)', padding: '10px 12px' }}>
    <div className="font-display" style={{ fontSize: 9, fontWeight: 600, letterSpacing: '0.10em', textTransform: 'uppercase', color: 'var(--text-muted)' }}>{label}</div>
    <div style={{ fontSize: 14, fontWeight: 600, fontFamily: 'var(--font-display)', color: accent || 'var(--text)', marginTop: 3 }}>{value}</div>
  </div>
);

const DriftItem = ({ drift, onApply }) => {
  const meta = {
    'email-changed': { icon: 'mail',      label: 'Email alterado' },
    'name-changed':  { icon: 'user',      label: 'Nome alterado' },
    'photo-changed': { icon: 'circle',    label: 'Foto alterada' },
    'left-ad':       { icon: 'warning',   label: 'Removido do AD' },
  }[drift.type] || { icon: 'circle', label: 'Alteração' };

  const isCritical = drift.type === 'left-ad';
  return (
    <div style={{
      padding: '10px 14px', borderBottom: '1px solid var(--border)',
      display: 'flex', flexDirection: 'column', gap: 6,
    }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
        <Icon name={meta.icon} size={11} style={{ color: isCritical ? 'var(--err)' : 'var(--text-muted)' }} />
        <span style={{ fontSize: 11, fontWeight: 600, color: isCritical ? 'var(--err)' : 'var(--text-muted)', fontFamily: 'var(--font-mono)', letterSpacing: '0.04em', textTransform: 'uppercase' }}>{meta.label}</span>
        <div style={{ flex: 1 }} />
        <span style={{ fontSize: 10, color: 'var(--text-dim)' }}>{drift.detected}</span>
      </div>
      <div style={{ fontSize: 12.5, color: 'var(--text)', fontWeight: 500 }}>{drift.user}</div>
      <div style={{ fontSize: 11, color: 'var(--text-muted)', fontFamily: 'var(--font-mono)', display: 'flex', alignItems: 'center', gap: 6, flexWrap: 'wrap' }}>
        <span style={{ textDecoration: 'line-through', color: 'var(--text-dim)' }}>{drift.from}</span>
        <Icon name="arrowUp" size={10} style={{ transform: 'rotate(90deg)', color: 'var(--text-dim)' }} />
        <span style={{ color: 'var(--text)' }}>{drift.to}</span>
      </div>
      <div style={{ display: 'flex', gap: 6, marginTop: 4 }}>
        <button onClick={onApply} className="btn btn-sm btn-primary" style={{ padding: '3px 10px', fontSize: 10.5 }}>
          <Icon name="check" size={10} />Aplicar
        </button>
        <button className="btn btn-sm btn-ghost" style={{ padding: '3px 10px', fontSize: 10.5 }}>
          Rejeitar
        </button>
      </div>
    </div>
  );
};

// ════════════════════════════════════════════════════════════════
// Inject keyframes
// ════════════════════════════════════════════════════════════════
if (typeof document !== 'undefined' && !document.getElementById('au-modals-keyframes')) {
  const s = document.createElement('style');
  s.id = 'au-modals-keyframes';
  s.textContent = `
    @keyframes modalIn { from { transform: translate(-50%,-50%) scale(0.96); opacity: 0; } to { transform: translate(-50%,-50%) scale(1); opacity: 1; } }
    @keyframes popIn   { from { transform: translateY(-6px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
    @keyframes spin    { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
    @keyframes pulse   { 0%,100% { opacity: 1; } 50% { opacity: 0.4; } }
    @keyframes toastIn { from { transform: translateY(8px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
  `;
  document.head.appendChild(s);
}

window.AdminUsersModals = { NovoUtilizadorModal, ImportCSVModal, SyncGraphPanel };
