/* global React, ReactDOM */
(function () {
  const { useState, useEffect, useCallback, useMemo, useRef } = React;
  const {
    apiFetch, toast, Icon, ConfirmDialog, LoadingSpinner, ErrorBanner, DataGrid,
    fetchListGridColumns, MapearColunasToolbarButton, getFieldValue, getItemFields, formatCellValue,
    EmailInputField, ContactoInputField, emailFieldError, digitsOnly, useOverlayDismiss,
    DatePickerField, TimePickerField, AppSelect,
  } = window.MyHabitaUi || {};

  function f(item, ...keys) {
    const src = item?.fields || item?.Fields || item || {};
    for (const k of keys) {
      const v = src[k];
      if (v == null || v === '') continue;
      const label = displayFieldValue(v);
      if (label !== '') return label;
      if (typeof v !== 'object') return v;
    }
    return '';
  }

  /** Resolve Graph lookup/choice/person objects and JSON image blobs to a display string. */
  function displayFieldValue(v) {
    if (v == null || v === '') return '';
    if (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean') return String(v);
    if (Array.isArray(v)) {
      return v.map(displayFieldValue).filter(Boolean).join(', ');
    }
    if (typeof v === 'object') {
      const label = v.lookupValue ?? v.LookupValue ?? v.value ?? v.Value
        ?? v.displayName ?? v.DisplayName ?? v.Title ?? v.title ?? v.Label ?? v.label
        ?? v.email ?? v.Email;
      if (label != null && label !== '' && typeof label !== 'object') return String(label);
      return '';
    }
    return String(v);
  }

  function lookupIdFrom(member, ...fieldKeys) {
    const src = member?.fields || member?.Fields || member || {};
    for (const key of fieldKeys) {
      const lid = src[`${key}LookupId`] ?? src[`${key}lookupId`];
      if (lid != null && lid !== '') return String(lid);
      const raw = src[key];
      if (raw && typeof raw === 'object') {
        const id = raw.lookupId ?? raw.LookupId ?? raw.id ?? raw.Id;
        if (id != null && id !== '') return String(id);
      }
    }
    return '';
  }

  function hasSharePointImage(member, ...fieldKeys) {
    const src = member?.fields || member?.Fields || member || {};
    for (const key of fieldKeys) {
      const raw = src[key];
      if (raw == null || raw === '') continue;
      if (typeof raw === 'string') {
        if (raw.trim().startsWith('{') && /fileName|serverRelativeUrl|thumbnailRenderer/i.test(raw)) return true;
        if (/\.(png|jpe?g|gif|webp|bmp)$/i.test(raw)) return true;
        continue;
      }
      if (typeof raw === 'object') {
        if (raw.fileName || raw.serverRelativeUrl || raw.thumbnailRenderer) return true;
        try {
          const s = JSON.stringify(raw);
          if (/fileName|serverRelativeUrl|thumbnailRenderer/i.test(s)) return true;
        } catch { /* ignore */ }
      }
    }
    return false;
  }

  async function loadLookupOptions(listKey) {
    try {
      const res = await apiFetch(`/api/lists/${encodeURIComponent(listKey)}/items?pageSize=200`);
      const items = res.items || res.Items || [];
      return items
        .map(i => ({
          id: String(itemId(i)),
          label: String(f(i, 'Title', 'title', 'Nome', 'Local', 'Assinatura') || itemId(i)),
        }))
        .filter(o => o.id)
        .sort((a, b) => a.label.localeCompare(b.label, 'pt', { sensitivity: 'base' }));
    } catch {
      return [];
    }
  }

  const itemId = i => i?.id || i?.Id || i?.ID || '';
  const pad = n => String(n).padStart(2, '0');

  function fmtDate(v, withTime) {
    if (!v) return '';
    const d = v instanceof Date ? v : new Date(v);
    if (Number.isNaN(d.getTime())) return String(v);
    return withTime
      ? `${d.toLocaleDateString('pt-PT', { day: '2-digit', month: '2-digit', year: 'numeric' })} ${pad(d.getHours())}:${pad(d.getMinutes())}`
      : d.toLocaleDateString('pt-PT', { day: '2-digit', month: '2-digit', year: 'numeric' });
  }

  function splitDateTimeLocal(v) {
    if (!v) return { date: '', time: '' };
    const s = String(v);
    const tIdx = s.indexOf('T');
    if (tIdx < 0) {
      if (/^\d{4}-\d{2}-\d{2}$/.test(s)) return { date: s, time: '' };
      const d = new Date(s);
      if (Number.isNaN(d.getTime())) return { date: '', time: '' };
      return { date: toDateInput(d), time: `${pad(d.getHours())}:${pad(d.getMinutes())}` };
    }
    return { date: s.slice(0, 10), time: s.slice(tIdx + 1, tIdx + 6) };
  }

  function joinDateTimeLocal(date, time) {
    if (!date) return '';
    return `${date}T${time || '09:00'}`;
  }

  function toDateInput(v) {
    if (!v) return '';
    const d = new Date(v);
    if (Number.isNaN(d.getTime())) return '';
    return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
  }

  function parseAgendaStart(item) {
    const raw = f(item, 'DataInicio', 'Data Inicio', 'Data_x0020_Inicio');
    if (!raw) return null;
    const d = new Date(raw);
    return Number.isNaN(d.getTime()) ? null : d;
  }

  function faseBadgeClass(fase) {
    const s = String(fase || '').toLowerCase();
    if (s.includes('agendar entrevista')) return 'fase-agendar';
    if (s.includes('entrevista agendada')) return 'fase-agendada';
    if (s === 'recrutado') return 'fase-recrutado';
    if (s.includes('empresa rejeitou') || s.includes('rejeitado habita')) return 'fase-rejeitado-habita';
    if (s.includes('candidato rejeitou') || s.includes('rejeitado candidato')) return 'fase-rejeitado-candidato';
    return 'fase-default';
  }

  function situacaoBarClass(sit) {
    const s = String(sit || '').toLowerCase();
    if (s.includes('confirm')) return 'sit-confirmada';
    if (s.includes('realiz')) return 'sit-realizada';
    if (s.includes('cancel')) return 'sit-cancelada';
    if (s.includes('reagend')) return 'sit-reagendada';
    return 'sit-agendada';
  }

  function ModalShell({ open, title, subtitle, onClose, children, footer, wide }) {
    const dismiss = useOverlayDismiss(onClose);
    if (!open) return null;
    return ReactDOM.createPortal(
      <div className="negocio-modal-overlay" role="presentation" {...dismiss}>
        <div className={`negocio-modal${wide ? ' equipa-modal-wide' : ''}`} role="dialog" aria-modal="true" onClick={e => e.stopPropagation()}>
          <div className="negocio-modal-panel">
            <div className="negocio-modal-header">
              <div>
                <h3>{title}</h3>
                {subtitle ? <p className="negocio-modal-subtitle">{subtitle}</p> : null}
              </div>
              <button type="button" className="negocio-modal-close" onClick={onClose} aria-label="Fechar">
                {Icon ? <Icon name="close" size={18} /> : '×'}
              </button>
            </div>
            <div className="negocio-modal-body">{children}</div>
            {footer ? <div className="negocio-modal-footer">{footer}</div> : null}
          </div>
        </div>
      </div>,
      document.body
    );
  }

  async function loadOperacoes() {
    try {
      const res = await apiFetch('/api/lists/operacoes/items?pageSize=100');
      return (res?.items || res?.Items || [])
        .map(i => f(i, 'Title', 'title') || i.title || '')
        .filter(Boolean)
        .sort((a, b) => a.localeCompare(b, 'pt'));
    } catch { return []; }
  }

  async function loadEquipaMembers() {
    const mapMember = m => {
      const email = String(f(m, 'Email', 'email') || m.email || m.Email || '').trim();
      const displayName = String(f(m, 'Assinatura', 'Nome', 'assinatura', 'nome') || m.assinatura || m.nome || email).trim();
      return email ? { email, displayName } : null;
    };
    try {
      let res;
      try { res = await apiFetch('/api/equipa/members?estado=Sim'); }
      catch { res = await apiFetch('/api/equipa/colaboradores?estado=Sim'); }
      return (res?.items || res?.Items || []).map(mapMember).filter(Boolean);
    } catch {
      try {
        const res = await apiFetch('/api/lists/himo-colaboradores/items?pageSize=200');
        return (res?.items || res?.Items || [])
          .filter(i => {
            const act = String(f(i, 'Activo', 'Ativo') || '').toLowerCase();
            const emp = String(f(i, 'Empresa', 'Marca') || 'M2').toLowerCase();
            return act === 'sim' && (emp.includes('m2') || emp.includes('habita') || !emp);
          })
          .map(mapMember).filter(Boolean);
      } catch { return []; }
    }
  }

  function Field({ label, children, full }) {
    return <div className={`form-group${full ? ' form-group-full' : ''}`}><label>{label}</label>{children}</div>;
  }

  function ModalFooter({ onClose, onSave, busy, saveLabel = 'Guardar' }) {
    return (
      <>
        <button type="button" className="btn btn-secondary" onClick={onClose} disabled={busy}>Cancelar</button>
        <button type="button" className="btn btn-primary" onClick={onSave} disabled={busy}>{busy ? 'A processar…' : saveLabel}</button>
      </>
    );
  }

  /* ── Recrutamento form ── */
  function RecrutamentoFormModal({ open, mode, item, onClose, onSaved }) {
    const empty = { localizacao: '', nome: '', sobrenome: '', telemovel: '', email: '', tipo: '', funcao: '', datadeEntrada: '', observacoes: '', origem: '', outraOrigem: '' };
    const [ops, setOps] = useState([]);
    const [busy, setBusy] = useState(false);
    const [form, setForm] = useState(empty);
    const set = (k, v) => setForm(p => ({ ...p, [k]: v }));

    useEffect(() => {
      if (!open) return;
      loadOperacoes().then(setOps);
      if (mode === 'edit' && item) {
        setForm({
          localizacao: String(f(item, 'Localiza_x00e7__x00e3_o', 'Localizacao', 'Localização') || ''),
          nome: String(f(item, 'Nome') || ''),
          sobrenome: String(f(item, 'Sobrenome') || ''),
          telemovel: String(f(item, 'Telem_x00f3_vel', 'Telemovel', 'Telemóvel') || '').replace(/\D/g, '').slice(0, 9),
          email: String(f(item, 'Email') || ''),
          tipo: String(f(item, 'Tipo') || ''),
          funcao: String(f(item, 'Fun_x00e7__x00e3_o', 'Funcao', 'Função') || ''),
          datadeEntrada: toDateInput(f(item, 'DatadeEntrada', 'DataDeEntrada', 'Data de Entrada')),
          observacoes: String(f(item, 'Observa_x00e7__x00f5_es', 'Observacoes', 'Observações') || ''),
          origem: String(f(item, 'Origem') || ''),
          outraOrigem: String(f(item, 'OutraOrigem', 'Outra Origem') || ''),
        });
      } else setForm(empty);
    }, [open, mode, item]);

    const save = async () => {
      if (!form.localizacao.trim()) return toast?.warning?.('Indique a localização.');
      if (!form.nome.trim() || !form.sobrenome.trim()) return toast?.warning?.('Nome e sobrenome são obrigatórios.');
      if (!form.email.trim() || !form.telemovel.trim()) return toast?.warning?.('Email e telemóvel são obrigatórios.');
      if (emailFieldError?.(form.email)) return toast?.warning?.(emailFieldError(form.email));
      if (!/^\d{9}$/.test(digitsOnly?.(form.telemovel) || form.telemovel.replace(/\D/g, ''))) {
        return toast?.warning?.('O telemóvel só pode ter números (9 dígitos).');
      }
      if (!form.tipo.trim() || !form.funcao.trim()) return toast?.warning?.('Tipo e função são obrigatórios.');
      if (!form.datadeEntrada) return toast?.warning?.('Data de entrada é obrigatória.');
      const fields = {
        Localiza_x00e7__x00e3_o: form.localizacao.trim(),
        Nome: form.nome.trim(), Sobrenome: form.sobrenome.trim(),
        Telem_x00f3_vel: form.telemovel.replace(/\D/g, '').slice(0, 9),
        Email: form.email.trim(), Tipo: form.tipo.trim(), Fun_x00e7__x00e3_o: form.funcao.trim(),
        DatadeEntrada: new Date(form.datadeEntrada).toISOString(),
        Observa_x00e7__x00f5_es: form.observacoes || null, Origem: form.origem || null,
      };
      if (String(form.origem).toLowerCase() === 'outra') fields.OutraOrigem = form.outraOrigem || null;
      if (mode === 'add') {
        fields.Empresa = { Value: 'M2' };
        fields.Data = new Date().toISOString();
        fields.Fase = 'Agendar Entrevista';
      }
      setBusy(true);
      try {
        if (mode === 'edit') {
          await apiFetch(`/api/lists/himo-recrutamentos/items/${encodeURIComponent(itemId(item))}`, { method: 'PATCH', body: JSON.stringify({ fields }) });
          toast?.success?.('Candidato atualizado.');
        } else {
          await apiFetch('/api/lists/himo-recrutamentos/items', { method: 'POST', body: JSON.stringify({ fields }) });
          toast?.success?.('Candidato inserido.');
        }
        onSaved?.(); onClose?.();
      } catch (err) { toast?.error?.(err.message || 'Erro ao guardar candidato.'); }
      finally { setBusy(false); }
    };

    return (
      <ModalShell open={open} title={mode === 'edit' ? 'Editar candidato' : 'Novo candidato'} subtitle="Recrutamentos HABITA (M2)" onClose={busy ? undefined : onClose} wide footer={<ModalFooter onClose={onClose} onSave={save} busy={busy} />}>
        <div className="negocio-modal-grid">
          <Field label="Localização *">
            <AppSelect
              value={form.localizacao}
              emptyOption="—"
              options={ops}
              onChange={v => set('localizacao', v)}
              ariaLabel="Localização"
            />
          </Field>
          <Field label="Data de Entrada *">
            <DatePickerField value={form.datadeEntrada} onChange={v => set('datadeEntrada', v)} />
          </Field>
          <Field label="Nome *"><input value={form.nome} onChange={e => set('nome', e.target.value)} /></Field>
          <Field label="Sobrenome *"><input value={form.sobrenome} onChange={e => set('sobrenome', e.target.value)} /></Field>
          <Field label="Telemóvel *">{ContactoInputField
            ? <ContactoInputField label="Telemóvel" maxLength={9} value={form.telemovel} onChange={v => set('telemovel', v)} />
            : <input value={form.telemovel} maxLength={9} inputMode="numeric" onChange={e => set('telemovel', e.target.value.replace(/\D/g, '').slice(0, 9))} />
          }</Field>
          <Field label="Email *">{EmailInputField
            ? <EmailInputField label="Email" errorId="recrutamento-email-error" value={form.email} onChange={v => set('email', v)} />
            : <input type="email" value={form.email} onChange={e => set('email', e.target.value)} />
          }</Field>
          <Field label="Tipo *"><input value={form.tipo} placeholder="Ex: Interno / Externo" onChange={e => set('tipo', e.target.value)} /></Field>
          <Field label="Função *"><input value={form.funcao} placeholder="Ex: Consultor" onChange={e => set('funcao', e.target.value)} /></Field>
          <Field label="Origem"><input value={form.origem} placeholder="Ex: LinkedIn, Outra…" onChange={e => set('origem', e.target.value)} /></Field>
          {String(form.origem).toLowerCase() === 'outra' && <Field label="Outra origem"><input value={form.outraOrigem} onChange={e => set('outraOrigem', e.target.value)} /></Field>}
        </div>
        <Field label="Observações" full><textarea rows={3} value={form.observacoes} onChange={e => set('observacoes', e.target.value)} /></Field>
      </ModalShell>
    );
  }

  function StateRecrutamentoModal({ open, item, onClose, onSaved }) {
    const fase = String(f(item, 'Fase') || '');
    const options = fase === 'Agendar Entrevista'
      ? ['Entrevista Agendada', 'Empresa Rejeitou', 'Candidato Rejeitou']
      : fase === 'Entrevista Agendada' ? ['Recrutado', 'Empresa Rejeitou', 'Candidato Rejeitou'] : [];
    const [chosen, setChosen] = useState('');
    const [busy, setBusy] = useState(false);
    useEffect(() => { if (open) setChosen(options[0] || ''); }, [open, fase]);

    const save = async () => {
      if (!chosen) return;
      setBusy(true);
      try {
        await apiFetch(`/api/lists/himo-recrutamentos/items/${encodeURIComponent(itemId(item))}`, { method: 'PATCH', body: JSON.stringify({ fields: { Fase: chosen } }) });
        toast?.success?.('Fase atualizada.'); onSaved?.(); onClose?.();
      } catch (err) { toast?.error?.(err.message || 'Erro ao atualizar fase.'); }
      finally { setBusy(false); }
    };

    return (
      <ModalShell open={open} title="Alterar fase" subtitle={`${f(item, 'Nome')} ${f(item, 'Sobrenome')}`.trim()} onClose={busy ? undefined : onClose} footer={<ModalFooter onClose={onClose} onSave={save} busy={busy || !chosen} />}>
        <Field label={`Fase atual: ${fase || '—'}`}>
          <div className="recrutamento-fase-radios">
            {options.map(o => (
              <label key={o} className="recrutamento-fase-radio">
                <input type="radio" name="fase" checked={chosen === o} onChange={() => setChosen(o)} />
                <span className={`recrutamento-fase-badge ${faseBadgeClass(o)}`}>{o}</span>
              </label>
            ))}
            {!options.length && <p className="empty-hint">Sem transições disponíveis para esta fase.</p>}
          </div>
        </Field>
      </ModalShell>
    );
  }

  function AddEntrevistaModal({ open, candidato, reagendar, previousAgendaId, onClose, onSaved }) {
    const [members, setMembers] = useState([]);
    const [selectedEmails, setSelectedEmails] = useState([]);
    const [extraEmails, setExtraEmails] = useState('');
    const [form, setForm] = useState({ dataInicio: '', hours: '0', minutes: '30', tipo: '', observacoes: '', confirmada: false });
    const [busy, setBusy] = useState(false);

    useEffect(() => {
      if (!open) return;
      loadEquipaMembers().then(setMembers);
      setSelectedEmails([]); setExtraEmails('');
      setForm({ dataInicio: '', hours: '0', minutes: '30', tipo: '', observacoes: '', confirmada: false });
    }, [open, candidato]);

    const save = async () => {
      if (!candidato || !form.dataInicio) return toast?.warning?.('Indique a data/hora de início.');
      const durationMinutes = (parseInt(form.hours, 10) || 0) * 60 + (parseInt(form.minutes, 10) || 0);
      if (durationMinutes < 30) return toast?.warning?.('Duração mínima: 30 minutos.');
      const entrevistadores = [
        ...selectedEmails.map(email => ({ email, displayName: members.find(x => x.email === email)?.displayName || email })),
        ...extraEmails.split(/[\n,;]+/).map(s => s.trim()).filter(Boolean).map(email => ({ email, displayName: email })),
      ];
      const candEmail = String(f(candidato, 'Email') || '');
      setBusy(true);
      try {
        await apiFetch('/api/recrutamento/agenda', {
          method: 'POST',
          body: JSON.stringify({
            candidatoId: String(itemId(candidato)), candidatoEmail: candEmail,
            candidatoLabel: `${f(candidato, 'Nome')} ${f(candidato, 'Sobrenome')}`.trim() || candEmail,
            dataInicio: new Date(form.dataInicio).toISOString(), durationMinutes,
            tipo: form.tipo || null, observacoes: form.observacoes || null, entrevistadores,
            reagendar: Boolean(reagendar), previousAgendaId: previousAgendaId || null, confirmada: Boolean(form.confirmada),
          }),
        });
        toast?.success?.(reagendar ? 'Entrevista reagendada.' : 'Entrevista agendada.');
        onSaved?.(); onClose?.();
      } catch (err) { toast?.error?.(err.message || 'Erro ao agendar entrevista.'); }
      finally { setBusy(false); }
    };

    return (
      <ModalShell open={open} title={reagendar ? 'Reagendar entrevista' : 'Agendar entrevista'} subtitle={candidato ? `${f(candidato, 'Nome')} ${f(candidato, 'Sobrenome')}`.trim() : ''} onClose={busy ? undefined : onClose} wide footer={<ModalFooter onClose={onClose} onSave={save} busy={busy} />}>
        <Field label="Candidato"><input readOnly value={String(f(candidato, 'Email') || '')} /></Field>
        <div className="negocio-modal-grid">
          <Field label="Data início *">
            <DatePickerField
              value={splitDateTimeLocal(form.dataInicio).date}
              onChange={d => setForm({ ...form, dataInicio: joinDateTimeLocal(d, splitDateTimeLocal(form.dataInicio).time) })}
            />
          </Field>
          <Field label="Hora *">
            <TimePickerField
              value={splitDateTimeLocal(form.dataInicio).time}
              onChange={t => setForm({ ...form, dataInicio: joinDateTimeLocal(splitDateTimeLocal(form.dataInicio).date, t) })}
            />
          </Field>
          <Field label="Duração (≥ 30 min)">
            <div className="recrutamento-duration">
              <input type="number" min="0" max="8" value={form.hours} onChange={e => setForm({ ...form, hours: e.target.value })} aria-label="Horas" /><span>h</span>
              <input type="number" min="0" max="59" step="5" value={form.minutes} onChange={e => setForm({ ...form, minutes: e.target.value })} aria-label="Minutos" /><span>min</span>
            </div>
          </Field>
          <Field label="Tipo"><input value={form.tipo} onChange={e => setForm({ ...form, tipo: e.target.value })} /></Field>
          <Field label=""><label><input type="checkbox" checked={form.confirmada} onChange={e => setForm({ ...form, confirmada: e.target.checked })} /> Já confirmada</label></Field>
        </div>
        <Field label="Entrevistadores">
          {members.length ? (
            <div className="recrutamento-entrevistadores">
              {members.slice(0, 80).map(m => (
                <label key={m.email} className="recrutamento-entrevistador-item">
                  <input type="checkbox" checked={selectedEmails.includes(m.email)} onChange={() => setSelectedEmails(p => p.includes(m.email) ? p.filter(e => e !== m.email) : [...p, m.email])} />
                  <span>{m.displayName}</span><small>{m.email}</small>
                </label>
              ))}
            </div>
          ) : <p className="empty-hint">Sem lista de colaboradores — use emails abaixo.</p>}
          <textarea rows={2} placeholder="Emails extra (vírgula ou nova linha)" value={extraEmails} onChange={e => setExtraEmails(e.target.value)} />
        </Field>
        <Field label="Observações"><textarea rows={2} value={form.observacoes} onChange={e => setForm({ ...form, observacoes: e.target.value })} /></Field>
      </ModalShell>
    );
  }

  const RECRUTAMENTO_LIST_KEY = 'himo-recrutamentos';
  /** Temporary: calendar panel hidden until agenda UX is ready. */
  const SHOW_RECRUTAMENTO_CALENDARIO = false;

  const RECRUTAMENTO_FALLBACK_COLUMNS = [
    { key: 'Nome', label: 'Nome', visible: true, order: 0 },
    { key: 'Sobrenome', label: 'Sobrenome', visible: true, order: 1 },
    { key: 'Email', label: 'Email', visible: true, order: 2, fill: true },
    { key: 'Localiza_x00e7__x00e3_o', label: 'Localização', visible: true, order: 3 },
    { key: 'Fase', label: 'Fase', visible: true, order: 4, filterType: 'choice' },
    { key: 'Tipo', label: 'Tipo', visible: true, order: 5, filterType: 'choice' },
    { key: 'Fun_x00e7__x00e3_o', label: 'Função', visible: true, order: 6, filterType: 'choice' },
    { key: 'Telem_x00f3_vel', label: 'Telemóvel', visible: true, order: 7 },
    { key: 'Data', label: 'Data', visible: true, order: 8, format: 'date' },
    { key: 'DatadeEntrada', label: 'Data de Entrada', visible: true, order: 9, format: 'date' },
  ];

  async function enrichRecrutamentoColumnChoices(defs) {
    if (!defs?.length || !apiFetch) return defs || [];
    try {
      const spCols = await apiFetch(`/api/settings/lists/by-key/${encodeURIComponent(RECRUTAMENTO_LIST_KEY)}/columns`);
      const list = Array.isArray(spCols) ? spCols : (spCols?.columns || spCols?.Columns || []);
      const byName = new Map((list || []).map(c => [String(c.name || c.Name || ''), c]));
      return defs.map(col => {
        const sp = byName.get(col.key);
        const choices = sp?.choices || sp?.Choices;
        if (!Array.isArray(choices) || !choices.length) return col;
        return { ...col, filterType: col.filterType || 'choice', choices: choices.map(c => String(c)) };
      });
    } catch {
      return defs;
    }
  }

  function candidatoToGridRow(item) {
    const fields = getItemFields ? getItemFields(item) : (item?.fields || item?.Fields || item || {});
    const id = itemId(item);
    return { ...fields, id, Id: id, _item: item };
  }

  function EquipaRecrutamentoPanel({ user, onConfigureListColumns = null }) {
    const [candidatos, setCandidatos] = useState([]);
    const [agenda, setAgenda] = useState([]);
    const [gridColumns, setGridColumns] = useState(RECRUTAMENTO_FALLBACK_COLUMNS);
    const [columnsFromSettings, setColumnsFromSettings] = useState(false);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState(null);
    const [selected, setSelected] = useState(null);
    const [showAllAgenda, setShowAllAgenda] = useState(false);
    const [dayOpen, setDayOpen] = useState(null);
    const [formMode, setFormMode] = useState(null);
    const [faseOpen, setFaseOpen] = useState(false);
    const [entrevista, setEntrevista] = useState(null);

    useEffect(() => {
      let cancelled = false;
      (async () => {
        if (!fetchListGridColumns) return;
        const { defs, fromSettings } = await fetchListGridColumns(RECRUTAMENTO_LIST_KEY, RECRUTAMENTO_FALLBACK_COLUMNS);
        const enriched = await enrichRecrutamentoColumnChoices(defs?.length ? defs : RECRUTAMENTO_FALLBACK_COLUMNS);
        if (!cancelled) {
          setGridColumns(enriched);
          setColumnsFromSettings(Boolean(fromSettings));
        }
      })();
      return () => { cancelled = true; };
    }, []);

    const loadAll = useCallback(async () => {
      setLoading(true); setError(null);
      try {
        const [cRes, aRes] = await Promise.allSettled([
          apiFetch('/api/recrutamento/candidatos'),
          apiFetch(`/api/recrutamento/agenda?all=${showAllAgenda ? 'true' : 'false'}`),
        ]);
        if (cRes.status === 'rejected') throw cRes.reason;
        setCandidatos(cRes.value?.items || cRes.value?.Items || []);
        setAgenda(aRes.status === 'fulfilled' ? (aRes.value?.items || aRes.value?.Items || []) : []);
      } catch (err) { setError({ message: err.message, code: err.code }); }
      finally { setLoading(false); }
    }, [showAllAgenda]);

    useEffect(() => { loadAll(); }, [loadAll]);

    const selectedFase = selected ? String(f(selected, 'Fase') || '') : '';
    const canAgendar = selectedFase === 'Agendar Entrevista';
    const canFaseEdit = canAgendar || selectedFase === 'Entrevista Agendada';

    const rows = useMemo(
      () => candidatos.map(candidatoToGridRow),
      [candidatos]
    );

    const agendaByDay = useMemo(() => {
      const map = new Map();
      for (const item of agenda) {
        const start = parseAgendaStart(item);
        if (!start || (!showAllAgenda && start < new Date())) continue;
        const key = start.toISOString().slice(0, 10);
        if (!map.has(key)) map.set(key, []);
        map.get(key).push({ item, start });
      }
      return [...map.entries()].sort((a, b) => a[0].localeCompare(b[0])).map(([dateKey, list]) => ({
        dateKey, label: fmtDate(dateKey), items: list.sort((a, b) => a.start - b.start),
      }));
    }, [agenda, showAllAgenda]);

    const patchSituacao = async (id, situacao) => {
      try {
        await apiFetch(`/api/recrutamento/agenda/${encodeURIComponent(id)}/situacao`, { method: 'PATCH', body: JSON.stringify({ situacao }) });
        toast?.success?.(`Situação: ${situacao}`); loadAll();
      } catch (err) { toast?.error?.(err.message || 'Erro ao atualizar situação.'); }
    };

    const dayItems = dayOpen ? (agendaByDay.find(d => d.dateKey === dayOpen)?.items || []) : [];

    return (
      <div className={`equipa-split recrutamento-panel${SHOW_RECRUTAMENTO_CALENDARIO ? '' : ' recrutamento-panel--no-calendario'}`}>
        {loading && <LoadingSpinner message="A carregar recrutamento…" />}
        {error && <ErrorBanner error={error} onDismiss={() => setError(null)} />}
        {!loading && !error && (
          <>
            <section className="equipa-split-left recrutamento-candidatos">
              <div className="toolbar recrutamento-toolbar">
                <div className="toolbar-actions">
                  <button type="button" className="btn btn-secondary btn-sm" onClick={loadAll}>Atualizar</button>
                  <button type="button" className="btn btn-secondary btn-sm" disabled={!canAgendar} onClick={() => setEntrevista({ reagendar: false })}>Agendar</button>
                  <button type="button" className="btn btn-secondary btn-sm" disabled={!canFaseEdit} onClick={() => setFaseOpen(true)}>Fase</button>
                  <button type="button" className="btn btn-secondary btn-sm" disabled={!canFaseEdit} onClick={() => setFormMode('edit')}>Editar</button>
                  {MapearColunasToolbarButton ? (
                    <MapearColunasToolbarButton
                      user={user}
                      listKey={RECRUTAMENTO_LIST_KEY}
                      onConfigureListColumns={onConfigureListColumns}
                    />
                  ) : null}
                  <button type="button" className="btn btn-primary btn-sm" onClick={() => setFormMode('add')}>INSERIR</button>
                </div>
              </div>
              <div className="module-page-table recrutamento-candidatos-table">
                <DataGrid
                  storageKey={RECRUTAMENTO_LIST_KEY}
                  columns={gridColumns}
                  rows={rows}
                  selectedId={selected ? itemId(selected) : null}
                  onRowClick={row => setSelected(row._item || row)}
                  emptyMessage="Sem candidatos."
                  preferSourceOrder={columnsFromSettings}
                  renderCell={(row, col) => {
                    const key = String(col.key || '').toLowerCase();
                    if (key === 'fase') {
                      const raw = getFieldValue ? getFieldValue(row, col.key) : f(row, 'Fase');
                      const label = formatCellValue
                        ? formatCellValue(raw, col.key, col.format)
                        : String(raw || '—');
                      return (
                        <span className={`recrutamento-fase-badge ${faseBadgeClass(label)}`}>
                          {label}
                        </span>
                      );
                    }
                    return undefined;
                  }}
                />
                <div className="pagination">
                  <span>Linhas: {rows.length}</span>
                </div>
              </div>
            </section>
            {SHOW_RECRUTAMENTO_CALENDARIO ? (
            <section className="equipa-split-right recrutamento-calendario">
              <div className="toolbar recrutamento-toolbar">
                <div className="toolbar-filters">
                  <strong>Calendário</strong>
                </div>
                <div className="toolbar-actions">
                  <label className="recrutamento-tudo-toggle">
                    <input type="checkbox" checked={showAllAgenda} onChange={e => setShowAllAgenda(e.target.checked)} /> Tudo
                  </label>
                </div>
              </div>
              <div className="recrutamento-agenda-list">
                {!agendaByDay.length && <p className="empty-hint">Sem entrevistas{showAllAgenda ? '.' : ' futuras.'}</p>}
                {agendaByDay.map(day => (
                  <button key={day.dateKey} type="button" className={`recrutamento-agenda-day${dayOpen === day.dateKey ? ' active' : ''}`} onClick={() => setDayOpen(dayOpen === day.dateKey ? null : day.dateKey)}>
                    <span className="recrutamento-agenda-day-label">{day.label}</span>
                    <span className="recrutamento-agenda-day-count">{day.items.length}</span>
                  </button>
                ))}
              </div>
              {dayOpen && (
                <div className="recrutamento-agenda-detail">
                  <h4>{fmtDate(dayOpen)}</h4>
                  {dayItems.map(({ item, start }) => {
                    const sit = String(f(item, 'Situa_x00e7__x00e3_o', 'Situacao', 'Situação') || 'Agendada');
                    return (
                      <div key={itemId(item)} className={`recrutamento-agenda-card ${situacaoBarClass(sit)}`}>
                        <div className="recrutamento-agenda-card-bar" aria-hidden="true" />
                        <div className="recrutamento-agenda-card-body">
                          <strong>{f(item, 'Title', 'Candidato') || 'Entrevista'}</strong>
                          <div>{fmtDate(start, true)} · {sit}</div>
                          <div className="recrutamento-agenda-actions">
                            <button type="button" className="btn btn-secondary btn-sm" onClick={() => patchSituacao(itemId(item), 'Confirmada')}>Confirmar</button>
                            <button type="button" className="btn btn-secondary btn-sm" onClick={() => patchSituacao(itemId(item), 'Realizada')}>Realizada</button>
                            <button type="button" className="btn btn-secondary btn-sm" onClick={() => {
                              const email = String(f(item, 'Candidato', 'Email') || '').toLowerCase();
                              const cand = (selected && String(f(selected, 'Email')).toLowerCase() === email ? selected : null)
                                || candidatos.find(c => String(f(c, 'Email')).toLowerCase() === email) || selected;
                              if (!cand) return toast?.warning?.('Selecione o candidato à esquerda para reagendar.');
                              setSelected(cand);
                              setEntrevista({ reagendar: true, previousAgendaId: itemId(item) });
                            }}>Reagendar</button>
                            <button type="button" className="btn btn-danger btn-sm" onClick={() => patchSituacao(itemId(item), 'Cancelada')}>Cancelar</button>
                          </div>
                        </div>
                      </div>
                    );
                  })}
                </div>
              )}
            </section>
            ) : null}
          </>
        )}
        <RecrutamentoFormModal open={formMode === 'add' || formMode === 'edit'} mode={formMode === 'edit' ? 'edit' : 'add'} item={formMode === 'edit' ? selected : null} onClose={() => setFormMode(null)} onSaved={loadAll} />
        <StateRecrutamentoModal open={faseOpen && !!selected} item={selected} onClose={() => setFaseOpen(false)} onSaved={loadAll} />
        <AddEntrevistaModal open={!!entrevista && !!selected} candidato={selected} reagendar={entrevista?.reagendar} previousAgendaId={entrevista?.previousAgendaId} onClose={() => setEntrevista(null)} onSaved={loadAll} />
      </div>
    );
  }

  /* ── CMI ── */
  const TPL_COLUMNS = [
    { key: 'tipo', label: 'Tipo', visible: true, order: 0 },
    { key: 'assunto', label: 'Assunto', visible: true, order: 1, fill: true },
    { key: 'textoApresentacao', label: 'Texto de Apresentação', visible: true, order: 2 },
    { key: 'emailRemetente', label: 'Email remetente', visible: true, order: 3 },
  ];

  function EquipaCmiPanel() {
    const [tool, setTool] = useState(1);
    const [templates, setTemplates] = useState([]);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState(null);
    const [selected, setSelected] = useState(null);
    const [viewHtml, setViewHtml] = useState(false);
    const [editOpen, setEditOpen] = useState(false);
    const [sendOpen, setSendOpen] = useState(false);
    const [editForm, setEditForm] = useState({});
    const [recipients, setRecipients] = useState('');
    const [memberPick, setMemberPick] = useState([]);
    const [members, setMembers] = useState([]);
    const [busy, setBusy] = useState(false);

    const load = useCallback(async () => {
      setLoading(true); setError(null);
      try {
        const res = await apiFetch('/api/notificacoes/email-templates');
        const items = res?.items || res?.Items || [];
        setTemplates([...items].sort((a, b) => (a.ordem ?? a.Ordem ?? 0) - (b.ordem ?? b.Ordem ?? 0)));
      } catch (err) { setError({ message: err.message, code: err.code }); }
      finally { setLoading(false); }
    }, []);
    useEffect(() => { load(); }, [load]);

    const rows = useMemo(() => templates.map(t => ({
      id: t.id || t.Id,
      tipo: t.tipo || t.Tipo || '',
      assunto: t.assunto || t.Assunto || '',
      textoApresentacao: t.textoApresentacao || t.TextoApresentacao || '',
      emailRemetente: t.emailRemetente || t.EmailRemetente || '',
      _item: t,
    })), [templates]);

    const openEdit = () => {
      if (!selected) return;
      setEditForm({
        assunto: selected.assunto || selected.Assunto || '',
        textoApresentacao: selected.textoApresentacao || selected.TextoApresentacao || '',
        corpoHtml: selected.corpoHtml || selected.CorpoHtml || '',
        emailRemetente: selected.emailRemetente || selected.EmailRemetente || '',
        tipo: selected.tipo || selected.Tipo || '',
      });
      setEditOpen(true);
    };

    const saveEdit = async () => {
      setBusy(true);
      try {
        await apiFetch(`/api/notificacoes/email-templates/${encodeURIComponent(selected.id || selected.Id)}`, {
          method: 'PATCH',
          body: JSON.stringify({ assunto: editForm.assunto, corpoHtml: editForm.corpoHtml, textoApresentacao: editForm.textoApresentacao, emailRemetente: editForm.emailRemetente, tipo: editForm.tipo }),
        });
        toast?.success?.('Template atualizado.'); setEditOpen(false); load();
      } catch (err) { toast?.error?.(err.message || 'Erro ao guardar template.'); }
      finally { setBusy(false); }
    };

    const openSend = async () => {
      if (!selected) return;
      setRecipients(''); setMemberPick([]);
      setMembers(await loadEquipaMembers());
      setSendOpen(true);
    };

    const doSend = async () => {
      const unique = [...new Set([...memberPick, ...recipients.split(/[\n,;]+/).map(s => s.trim()).filter(Boolean)].map(e => e.toLowerCase()))];
      if (!unique.length) return toast?.warning?.('Indique pelo menos um destinatário.');
      setBusy(true);
      try {
        const result = await apiFetch('/api/notificacoes/cmi/enviar', { method: 'POST', body: JSON.stringify({ templateId: String(selected.id || selected.Id), recipientEmails: unique }) });
        toast?.success?.(`Enviado: ${result?.sent ?? result?.Sent ?? 0}${(result?.failed ?? result?.Failed) ? ` · falhas: ${result.failed ?? result.Failed}` : ''}`);
        setSendOpen(false);
      } catch (err) { toast?.error?.(err.message || 'Erro ao enviar.'); }
      finally { setBusy(false); }
    };

    const html = selected ? (selected.corpoHtml || selected.CorpoHtml || '') : '';

    return (
      <div className="equipa-cmi-panel equipa-split">
        <aside className="equipa-cmi-tools">
          <button type="button" className={`equipa-cmi-tool${tool === 1 ? ' active' : ''}`} onClick={() => setTool(1)}>Templates de Email</button>
          <button type="button" className="equipa-cmi-tool" disabled title="Em breve">Entradas</button>
          <button type="button" className="equipa-cmi-tool" disabled title="Em breve">WIP</button>
        </aside>
        <div className="equipa-cmi-main">
          {tool === 1 && (
            <>
              <div className="toolbar recrutamento-toolbar">
                <div className="toolbar-actions">
                  <button type="button" className="btn btn-secondary btn-sm" onClick={load}>Atualizar</button>
                  <button type="button" className="btn btn-secondary btn-sm" disabled={!selected} onClick={() => setViewHtml(true)}>Visualizar</button>
                  <button type="button" className="btn btn-secondary btn-sm" disabled={!selected} onClick={openSend}>Enviar</button>
                  <button type="button" className="btn btn-primary btn-sm" disabled={!selected} onClick={openEdit}>EDITAR</button>
                </div>
              </div>
              {loading && <LoadingSpinner message="A carregar templates…" />}
              {error && <ErrorBanner error={error} onDismiss={() => setError(null)} />}
              {!loading && !error && (
                <div className="module-page-table">
                  <DataGrid storageKey="equipa-cmi-templates" columns={TPL_COLUMNS} rows={rows} selectedId={selected ? (selected.id || selected.Id) : null} onRowClick={row => setSelected(row._item)} emptyMessage="Sem templates de email." showColumnChooser={false} />
                </div>
              )}
            </>
          )}
        </div>
        <ModalShell open={viewHtml && !!selected} title="Visualizar template" subtitle={selected?.assunto || selected?.Assunto} onClose={() => setViewHtml(false)} wide>
          {html ? <iframe title="Preview" className="cmi-html-preview" sandbox="" srcDoc={html} /> : <p className="empty-hint">Sem HTML.</p>}
        </ModalShell>
        <ModalShell open={editOpen} title="Editar template" onClose={busy ? undefined : () => setEditOpen(false)} wide footer={<ModalFooter onClose={() => setEditOpen(false)} onSave={saveEdit} busy={busy} />}>
          <div className="negocio-modal-grid">
            <Field label="Tipo"><input value={editForm.tipo || ''} onChange={e => setEditForm({ ...editForm, tipo: e.target.value })} /></Field>
            <Field label="Email remetente"><input value={editForm.emailRemetente || ''} onChange={e => setEditForm({ ...editForm, emailRemetente: e.target.value })} /></Field>
          </div>
          <Field label="Assunto"><input value={editForm.assunto || ''} onChange={e => setEditForm({ ...editForm, assunto: e.target.value })} /></Field>
          <Field label="Texto de apresentação"><input value={editForm.textoApresentacao || ''} onChange={e => setEditForm({ ...editForm, textoApresentacao: e.target.value })} /></Field>
          <Field label="Corpo HTML"><textarea rows={12} value={editForm.corpoHtml || ''} onChange={e => setEditForm({ ...editForm, corpoHtml: e.target.value })} /></Field>
        </ModalShell>
        <ModalShell open={sendOpen} title="Enviar template" subtitle={selected?.assunto || selected?.Assunto} onClose={busy ? undefined : () => setSendOpen(false)} wide footer={<ModalFooter onClose={() => setSendOpen(false)} onSave={doSend} busy={busy} saveLabel="Enviar" />}>
          {!!members.length && (
            <Field label="Equipa">
              <div className="recrutamento-entrevistadores">
                {members.slice(0, 80).map(m => (
                  <label key={m.email} className="recrutamento-entrevistador-item">
                    <input type="checkbox" checked={memberPick.includes(m.email)} onChange={() => setMemberPick(p => p.includes(m.email) ? p.filter(x => x !== m.email) : [...p, m.email])} />
                    <span>{m.displayName}</span><small>{m.email}</small>
                  </label>
                ))}
              </div>
            </Field>
          )}
          <Field label="Destinatários (vírgula ou nova linha)"><textarea rows={4} value={recipients} onChange={e => setRecipients(e.target.value)} placeholder="email@exemplo.com" /></Field>
        </ModalShell>
      </div>
    );
  }

  /* ── Colaborador options ── */
  function EquipaColaboradorOptions({ member, open, onClose, onEdited, canViewAs = false, onEnterAs = null }) {
    const [view, setView] = useState('options');
    const [confirmPw, setConfirmPw] = useState(false);
    const [confirmSaida, setConfirmSaida] = useState(false);
    const [confirmSyncPhoto, setConfirmSyncPhoto] = useState(false);
    const [busy, setBusy] = useState(false);
    const [form, setForm] = useState({});
    const [lookups, setLookups] = useState({
      funcoes: [], locais: [], departamentos: [], categorias: [],
      estadoCivil: [], genero: [],
    });
    const [photoFailed, setPhotoFailed] = useState(false);
    const [perfilFailed, setPerfilFailed] = useState(false);
    const [photoStamp, setPhotoStamp] = useState(0);
    const imagemInputRef = useRef(null);
    const perfilInputRef = useRef(null);
    const set = (k, v) => setForm(p => ({ ...p, [k]: v }));

    useEffect(() => {
      if (!open || !member) return;
      setView('options'); setConfirmPw(false); setConfirmSaida(false); setConfirmSyncPhoto(false);
      setPhotoFailed(false); setPerfilFailed(false); setPhotoStamp(0);
      setForm({
        hasImagem: hasSharePointImage(member, 'Imagem', 'Image', 'Foto'),
        hasImagemPerfil: hasSharePointImage(member, 'ImagemdePerfil', 'ImagemDePerfil'),
        nome: String(f(member, 'Nome', 'nome') || member.nome || ''),
        assinatura: String(f(member, 'Assinatura', 'assinatura') || member.assinatura || ''),
        dataNascimento: toDateInput(f(member, 'DataNascimento', 'Data de Nascimento', 'DatadeNascimento')),
        categoriaProfissionalLookupId: lookupIdFrom(member, 'CategoriaProfissional'),
        categoriaProfissionalLabel: String(f(member, 'CategoriaProfissional', 'Categoria Profissional') || ''),
        localLookupId: lookupIdFrom(member, 'Local'),
        localLabel: String(f(member, 'Local', 'local') || ''),
        departamentoLookupId: lookupIdFrom(member, 'Departamento'),
        departamentoLabel: String(f(member, 'Departamento', 'departamento') || ''),
        emailPessoal: String(f(member, 'EmailPessoal', 'Email Pessoal', 'Emailpessoal') || ''),
        estadoCivil: String(f(member, 'EstadoCivil', 'Estado Civil') || ''),
        funcaoLookupId: lookupIdFrom(member, 'Fun_x00e7__x00e3_o', 'Funcao'),
        funcaoLabel: String(f(member, 'Funcao', 'Função', 'Fun_x00e7__x00e3_o', 'funcao') || member.funcao || ''),
        genero: String(f(member, 'Genero', 'Género', 'G_x00e9_nero') || ''),
        morada: String(f(member, 'Morada', 'morada') || ''),
        nacionalidade: String(f(member, 'Nacionalidade') || ''),
        naturalidade: String(f(member, 'Naturalidade') || ''),
      });
    }, [open, member]);

    useEffect(() => {
      if (!open) return;
      let cancelled = false;
      (async () => {
        const [funcoes, locais, departamentos, categorias, cols] = await Promise.all([
          loadLookupOptions('himo-funcoes'),
          loadLookupOptions('himo-locais'),
          loadLookupOptions('himo-departamentos'),
          loadLookupOptions('himo-categoria-profissional'),
          apiFetch('/api/settings/lists/by-key/himo-colaboradores/columns').catch(() => []),
        ]);
        if (cancelled) return;
        const colList = Array.isArray(cols) ? cols : (cols?.items || cols || []);
        const findChoices = (names) => {
          const hit = colList.find(c => names.some(n => String(c.name || c.Name || '').toLowerCase() === n.toLowerCase()));
          const ch = hit?.choices || hit?.Choices || [];
          return Array.isArray(ch) ? ch.map(String) : [];
        };
        setLookups({
          funcoes, locais, departamentos, categorias,
          estadoCivil: findChoices(['EstadoCivil', 'Estado Civil']),
          genero: findChoices(['G_x00e9_nero', 'Genero', 'Género']),
        });
      })();
      return () => { cancelled = true; };
    }, [open]);

    if (!open || !member) return null;

    const activo = String(f(member, 'Activo', 'Ativo', 'activo') || member.activo || '').toLowerCase();
    const saidaDisabled = ['não', 'nao', 'saida', 'saída', 'transferência', 'transferencia'].includes(activo);
    const email = String(f(member, 'Email', 'email') || member.email || member.Email || '').trim();
    const id = itemId(member);
    const photoSrc = form.hasImagem && id
      ? `/api/lists/himo-colaboradores/items/${encodeURIComponent(id)}/image/Imagem?size=medium&v=${photoStamp || '1'}`
      : null;
    const perfilSrc = form.hasImagemPerfil && id
      ? `/api/lists/himo-colaboradores/items/${encodeURIComponent(id)}/image/ImagemdePerfil?size=large&v=${photoStamp || '1'}`
      : null;

    const resetPassword = async () => {
      setBusy(true);
      try {
        await apiFetch('/api/equipa/password-reset', { method: 'POST', body: JSON.stringify({ email }) });
        toast?.success?.('Pedido de redefinição enviado.'); setConfirmPw(false);
      } catch (err) { toast?.error?.(err.message || 'Erro ao redefinir palavra-passe.'); }
      finally { setBusy(false); }
    };

    const iniciarSaida = async () => {
      setBusy(true);
      try {
        await apiFetch(`/api/equipa/colaboradores/${encodeURIComponent(id)}`, { method: 'PATCH', body: JSON.stringify({ fields: { Activo: 'Saida' } }) });
        toast?.success?.('Processo de saída iniciado.'); setConfirmSaida(false); onEdited?.(); onClose?.();
      } catch (err) { toast?.error?.(err.message || 'Erro ao iniciar saída.'); }
      finally { setBusy(false); }
    };

    const uploadImageColumn = async (column, file) => {
      if (!id || !file) return;
      setBusy(true);
      try {
        const body = new FormData();
        body.append('file', file);
        const res = await fetch(`/api/equipa/colaboradores/${encodeURIComponent(id)}/image/${encodeURIComponent(column)}`, {
          method: 'POST',
          credentials: 'include',
          body,
        });
        const text = await res.text();
        let parsed = {};
        try { parsed = text ? JSON.parse(text) : {}; } catch { /* ignore */ }
        if (!res.ok) throw new Error(parsed.error || parsed.message || text || 'Falha ao carregar imagem.');
        if (column === 'Imagem') {
          set('hasImagem', true);
          setPhotoFailed(false);
        } else {
          set('hasImagemPerfil', true);
          setPerfilFailed(false);
        }
        setPhotoStamp(Date.now());
        toast?.success?.(column === 'Imagem' ? 'Imagem atualizada.' : 'Imagem de perfil atualizada.');
        onEdited?.();
      } catch (err) {
        toast?.error?.(err.message || 'Erro ao carregar imagem.');
      } finally {
        setBusy(false);
      }
    };

    const syncMicrosoftPhoto = async () => {
      setBusy(true);
      try {
        await apiFetch(`/api/equipa/colaboradores/${encodeURIComponent(id)}/sync-microsoft-photo`, { method: 'POST' });
        toast?.success?.('Imagem definida na conta Microsoft.');
        setConfirmSyncPhoto(false);
        onEdited?.({ syncedPhoto: true, email, id });
      } catch (err) {
        toast?.error?.(err.message || 'Erro ao definir imagem Microsoft.');
      } finally {
        setBusy(false);
      }
    };

    const saveEditor = async () => {
      if (!form.funcaoLookupId) return toast?.warning?.('Função é obrigatória.');
      if (emailFieldError?.(form.emailPessoal)) return toast?.warning?.(emailFieldError(form.emailPessoal));
      setBusy(true);
      try {
        await apiFetch(`/api/equipa/colaboradores/${encodeURIComponent(id)}`, {
          method: 'PATCH',
          body: JSON.stringify({
            fields: {
              Assinatura: form.assinatura || null,
              DataNascimento: form.dataNascimento ? new Date(form.dataNascimento).toISOString() : null,
              CategoriaProfissionalLookupId: form.categoriaProfissionalLookupId || null,
              LocalLookupId: form.localLookupId || null,
              DepartamentoLookupId: form.departamentoLookupId || null,
              EmailPessoal: form.emailPessoal || null,
              EstadoCivil: form.estadoCivil || null,
              Fun_x00e7__x00e3_oLookupId: form.funcaoLookupId || null,
              G_x00e9_nero: form.genero || null,
              Morada: form.morada || null,
              Nacionalidade: form.nacionalidade || null,
              Naturalidade: form.naturalidade || null,
            },
          }),
        });
        toast?.success?.('Dados atualizados.'); onEdited?.(); onClose?.();
      } catch (err) { toast?.error?.(err.message || 'Erro ao guardar dados.'); }
      finally { setBusy(false); }
    };

    const withCurrent = (options, id, label) => {
      if (!id) return options;
      if (options.some(o => String(o.id) === String(id))) return options;
      return [{ id: String(id), label: label || id }, ...options];
    };

    const LookupSelect = ({ label, value, onChange, options, required }) => (
      <Field label={required ? `${label} *` : label}>
        <AppSelect
          value={value || ''}
          emptyOption="—"
          options={options.map(o => ({ value: o.id, label: o.label }))}
          onChange={onChange}
          ariaLabel={label}
        />
      </Field>
    );

    const ChoiceSelect = ({ label, value, onChange, options }) => (
      <Field label={label}>
        {options.length ? (
          <AppSelect
            value={value || ''}
            emptyOption="—"
            options={options}
            onChange={onChange}
            ariaLabel={label}
          />
        ) : (
          <input value={value || ''} onChange={e => onChange(e.target.value)} />
        )}
      </Field>
    );

    return (
      <>
        <ModalShell open={open && view === 'options'} title="Opções do colaborador" subtitle={form.assinatura || form.nome || email} onClose={onClose} footer={<button type="button" className="btn btn-secondary" onClick={onClose}>Fechar</button>}>
          <div className="equipa-colab-options">
            {canViewAs && email ? (
              <button
                type="button"
                className="btn btn-secondary equipa-colab-option"
                disabled={busy}
                onClick={() => {
                  onClose?.();
                  onEnterAs?.(member);
                }}
              >
                Entrar como
              </button>
            ) : null}
            <button type="button" className="btn btn-secondary equipa-colab-option" onClick={() => setView('editor')}>Editar dados</button>
            <button type="button" className="btn btn-secondary equipa-colab-option" onClick={() => setConfirmPw(true)} disabled={!email}>Redefinir palavra-passe</button>
            <button
              type="button"
              className="btn btn-secondary equipa-colab-option"
              onClick={() => setConfirmSyncPhoto(true)}
              disabled={!email || !form.hasImagem || busy}
              title={!form.hasImagem ? 'Sem imagem na coluna SharePoint «Imagem»' : (!email ? 'Sem email Microsoft' : undefined)}
            >
              Definir imagem Microsoft
            </button>
            <button type="button" className="btn btn-secondary equipa-colab-option" onClick={() => setConfirmSaida(true)} disabled={saidaDisabled}>Iniciar processo de saída</button>
            <button type="button" className="btn btn-secondary equipa-colab-option" disabled>Editar escalão</button>
          </div>
        </ModalShell>
        <ModalShell open={open && view === 'editor'} title="A editar dados de" subtitle={form.assinatura || form.nome || email} onClose={busy ? undefined : () => setView('options')} wide footer={<>
          <button type="button" className="btn btn-secondary" onClick={() => setView('options')} disabled={busy}>Voltar</button>
          <button type="button" className="btn btn-primary" onClick={saveEditor} disabled={busy}>{busy ? 'A guardar…' : 'Guardar'}</button>
        </>}>
          <div className="negocio-modal-grid">
            <Field label="Imagem" full>
              <div className="equipa-colab-photo-field">
                {photoSrc && !photoFailed ? (
                  <img
                    src={photoSrc}
                    alt=""
                    className="equipa-colab-photo-preview"
                    loading="lazy"
                    referrerPolicy="no-referrer"
                    onError={() => setPhotoFailed(true)}
                  />
                ) : (
                  <span className="equipa-colab-photo-placeholder" aria-hidden="true" />
                )}
                <div className="equipa-colab-photo-actions">
                  <button
                    type="button"
                    className="btn btn-secondary btn-sm"
                    disabled={busy || !id}
                    onClick={() => imagemInputRef.current?.click()}
                  >
                    {form.hasImagem ? 'Alterar imagem' : 'Adicionar imagem'}
                  </button>
                  <span className="empty-hint">Foto usada no cartão e na conta Microsoft.</span>
                </div>
                <input
                  ref={imagemInputRef}
                  type="file"
                  accept="image/png,image/jpeg,image/webp"
                  hidden
                  onChange={(e) => {
                    const file = e.target.files?.[0];
                    e.target.value = '';
                    if (file) uploadImageColumn('Imagem', file);
                  }}
                />
              </div>
            </Field>
            <Field label="Imagem de perfil" full>
              <div className="equipa-colab-photo-field">
                {perfilSrc && !perfilFailed ? (
                  <img
                    src={perfilSrc}
                    alt=""
                    className="equipa-colab-photo-preview is-perfil"
                    loading="lazy"
                    referrerPolicy="no-referrer"
                    onError={() => setPerfilFailed(true)}
                  />
                ) : (
                  <span className="equipa-colab-photo-placeholder is-perfil" aria-hidden="true" />
                )}
                <div className="equipa-colab-photo-actions">
                  <button
                    type="button"
                    className="btn btn-secondary btn-sm"
                    disabled={busy || !id}
                    onClick={() => perfilInputRef.current?.click()}
                  >
                    {form.hasImagemPerfil ? 'Alterar imagem de perfil' : 'Adicionar imagem de perfil'}
                  </button>
                  <span className="empty-hint">Foto de corpo inteiro no verso do cartão.</span>
                </div>
                <input
                  ref={perfilInputRef}
                  type="file"
                  accept="image/png,image/jpeg,image/webp"
                  hidden
                  onChange={(e) => {
                    const file = e.target.files?.[0];
                    e.target.value = '';
                    if (file) uploadImageColumn('ImagemdePerfil', file);
                  }}
                />
              </div>
            </Field>
            <Field label="Nome"><input readOnly value={form.nome || ''} /></Field>
            <Field label="Assinatura"><input value={form.assinatura || ''} onChange={e => set('assinatura', e.target.value)} /></Field>
            <Field label="Data de Nascimento">
              <DatePickerField value={form.dataNascimento || ''} onChange={v => set('dataNascimento', v)} />
            </Field>
            <LookupSelect label="Categoria profissional" value={form.categoriaProfissionalLookupId} onChange={v => set('categoriaProfissionalLookupId', v)} options={withCurrent(lookups.categorias, form.categoriaProfissionalLookupId, form.categoriaProfissionalLabel)} />
            <LookupSelect label="Local" value={form.localLookupId} onChange={v => set('localLookupId', v)} options={withCurrent(lookups.locais, form.localLookupId, form.localLabel)} />
            <LookupSelect label="Departamento" value={form.departamentoLookupId} onChange={v => set('departamentoLookupId', v)} options={withCurrent(lookups.departamentos, form.departamentoLookupId, form.departamentoLabel)} />
            <Field label="Email pessoal">{EmailInputField
              ? <EmailInputField label="Email pessoal" errorId="colab-email-pessoal-error" value={form.emailPessoal || ''} onChange={v => set('emailPessoal', v)} />
              : <input type="email" value={form.emailPessoal || ''} onChange={e => set('emailPessoal', e.target.value)} />
            }</Field>
            <ChoiceSelect label="Estado civil" value={form.estadoCivil} onChange={v => set('estadoCivil', v)} options={lookups.estadoCivil} />
            <LookupSelect label="Função" value={form.funcaoLookupId} onChange={v => set('funcaoLookupId', v)} options={withCurrent(lookups.funcoes, form.funcaoLookupId, form.funcaoLabel)} required />
            <ChoiceSelect label="Género" value={form.genero} onChange={v => set('genero', v)} options={lookups.genero} />
            <Field label="Morada" full><input value={form.morada || ''} onChange={e => set('morada', e.target.value)} /></Field>
            <Field label="Nacionalidade"><input value={form.nacionalidade || ''} onChange={e => set('nacionalidade', e.target.value)} /></Field>
            <Field label="Naturalidade"><input value={form.naturalidade || ''} onChange={e => set('naturalidade', e.target.value)} /></Field>
          </div>
        </ModalShell>
        {ConfirmDialog && (
          <>
            <ConfirmDialog open={confirmPw} title="Redefinir palavra-passe" message={`Enviar pedido de redefinição para ${email}?`} confirmLabel="Redefinir" danger busy={busy} onConfirm={resetPassword} onCancel={() => setConfirmPw(false)} />
            <ConfirmDialog open={confirmSaida} title="Iniciar processo de saída" message={`Marcar ${form.assinatura || email} como Saída?`} confirmLabel="Iniciar saída" danger busy={busy} onConfirm={iniciarSaida} onCancel={() => setConfirmSaida(false)} />
            <ConfirmDialog
              open={confirmSyncPhoto}
              title="Definir imagem Microsoft"
              message={`Copiar a imagem SharePoint de ${form.assinatura || email} para a foto da conta Microsoft?`}
              confirmLabel="Definir imagem"
              busy={busy}
              onConfirm={syncMicrosoftPhoto}
              onCancel={() => setConfirmSyncPhoto(false)}
            />
          </>
        )}
      </>
    );
  }

  window.EquipaRecrutamentoPanel = EquipaRecrutamentoPanel;
  window.EquipaCmiPanel = EquipaCmiPanel;
  window.EquipaColaboradorOptions = EquipaColaboradorOptions;
  try {
    window.dispatchEvent(new Event('myhabita-equipa-ext-ready'));
  } catch { /* ignore */ }
})();
