// Colaboradores — workspace members + roles + invitations (Phase-2 + 3).
//
// Endpoints used:
//   GET  /api/workspace                  → workspace summary (name, member count)
//   GET  /api/workspace/members          → member list with role + effective perms
//   GET  /api/workspace/roles            → available roles (system + custom)
//   GET  /api/permissions                → atomic permission catalog
//   GET  /api/workspace/me/permissions   → my own effective perms (gates buttons)
//   PATCH /api/workspace/members/:userId → change a member's role
//   POST   /api/workspace/invites        → invite a collaborator by email + role
//   GET    /api/workspace/invites        → pending invites (member.invite required)
//   DELETE /api/workspace/invites/:id    → revoke a pending invite
//
// Live updates: subscribes to workspace.member.changed and workspace.role.changed
// on the realtime bus. Both invite create + accept emit member.changed events,
// so this view auto-refreshes when an invite is sent or accepted from anywhere.

const _CATEGORY_LABEL = {
  tasks: 'Tasks',
  docs: 'Documentos',
  phases: 'Fases',
  payments: 'Pagos',
  uploads: 'Uploads',
  members: 'Miembros',
  vps: 'VPS',
  code: 'Claude Code',
  mcp: 'MCP',
  logs: 'Bitácora',
  workspace: 'Workspace',
  projects: 'Proyectos',
};

function ColaboradoresView() {
  const [summary, setSummary] = React.useState(null);
  const [members, setMembers] = React.useState([]);
  const [invites, setInvites] = React.useState([]);
  const [roles, setRoles] = React.useState([]);
  const [permsCatalog, setPermsCatalog] = React.useState([]);
  const [myPerms, setMyPerms] = React.useState({ permissions: [], legacyBypass: false });
  const [loading, setLoading] = React.useState(true);
  const [error, setError] = React.useState(null);
  const [editingMember, setEditingMember] = React.useState(null);
  const [showInvite, setShowInvite] = React.useState(false);
  const [scope, setScope] = React.useState(() => {
    try { return localStorage.getItem('ws.colab.scope') === 'project' ? 'project' : 'workspace'; }
    catch { return 'workspace'; }
  });
  const [projectId, setProjectId] = React.useState(() => window.getCurrentProject?.()?.id || null);
  const [projectMembers, setProjectMembers] = React.useState([]);
  const [editingPM, setEditingPM] = React.useState(null);

  const canInvite = myPerms.legacyBypass || myPerms.permissions.includes('member.invite');

  const refresh = React.useCallback(async () => {
    setLoading(true);
    setError(null);
    try {
      const [s, m, r, p, mp] = await Promise.all([
        window.api('GET', '/api/workspace'),
        window.api('GET', '/api/workspace/members'),
        window.api('GET', '/api/workspace/roles'),
        window.api('GET', '/api/permissions'),
        window.api('GET', '/api/workspace/me/permissions'),
      ]);
      setSummary(s);
      setMembers(m.members);
      setRoles(r.roles);
      setPermsCatalog(p.permissions);
      setMyPerms(mp);
      // Invites are gated by member.invite — only fetch if we have it.
      const canList = mp.legacyBypass || mp.permissions.includes('member.invite');
      if (canList) {
        try {
          const inv = await window.api('GET', '/api/workspace/invites');
          setInvites(inv.invites);
        } catch { /* ignore — view still useful without invites list */ }
      }
    } catch (e) {
      setError(e?.message || 'No se pudo cargar');
    } finally {
      setLoading(false);
    }
  }, []);

  React.useEffect(() => { refresh(); }, [refresh]);

  const refreshProject = React.useCallback(async (pid) => {
    if (!pid) { setProjectMembers([]); return; }
    try {
      const r = await window.api('GET', `/api/projects/${pid}/members`);
      setProjectMembers(r.members);
    } catch { setProjectMembers([]); }
  }, []);

  React.useEffect(() => {
    if (scope === 'project') refreshProject(projectId);
  }, [scope, projectId, refreshProject]);

  // Live invalidation: any member or role change reloads the lists.
  React.useEffect(() => {
    return window.onRealtime?.((msg) => {
      if (msg.type === 'workspace.member.changed' || msg.type === 'workspace.role.changed') {
        refresh();
        if (scope === 'project') refreshProject(projectId);
      }
    });
  }, [refresh, scope, projectId, refreshProject]);

  const canUpdateRole = myPerms.legacyBypass || myPerms.permissions.includes('member.update_role');
  const canManageRoles = canUpdateRole; // same gate today; could split later
  const [editingRole, setEditingRole] = React.useState(null); // role row or 'new'

  return (
    <div style={{ flex: 1, overflowY: 'auto', padding: '32px 40px 80px' }}>
      <div style={{ maxWidth: 1080, margin: '0 auto' }}>
        <SectionHead
          level="h1"
          title="Colaboradores"
          sub={summary ? `${summary.name} · ${summary.memberCount} miembro${summary.memberCount === 1 ? '' : 's'} · plan ${summary.subscriptionTier}` : 'Cargando…'}
          right={
            canInvite ? (
              <Button variant="primary" size="md" icon="user-plus" onClick={() => setShowInvite(true)}>
                Invitar
              </Button>
            ) : (
              <Button variant="secondary" size="md" icon="user-plus" disabled title="Necesitas el permiso member.invite para invitar">
                Invitar
              </Button>
            )
          }
        />

        {error && (
          <div style={{
            marginBottom: 16, padding: '12px 16px',
            background: 'rgba(239,68,68,.08)', border: '1px solid rgba(239,68,68,.30)',
            borderRadius: 8, color: 'var(--danger)', fontSize: 13,
          }}>
            {error}
          </div>
        )}

        {loading && !summary ? (
          <div style={{ padding: 40, color: 'var(--text-2)' }}>Cargando…</div>
        ) : (
          <>
            <ScopeTabs scope={scope} setScope={setScope} projectId={projectId} setProjectId={setProjectId} />

            {scope === 'workspace' ? (
              <>
                <MembersTable
                  members={members}
                  roles={roles}
                  canUpdateRole={canUpdateRole}
                  onEdit={(m) => setEditingMember(m)}
                />

                {canInvite && invites.length > 0 && (
                  <>
                    <div style={{ height: 24 }} />
                    <SectionHead level="h2" title="Invitaciones pendientes" sub={`${invites.length} esperando ser aceptada${invites.length === 1 ? '' : 's'}`} />
                    <InvitesTable invites={invites} onRevoked={refresh} />
                  </>
                )}
              </>
            ) : (
              <ProjectMembersTable
                projectId={projectId}
                members={projectMembers}
                roles={roles}
                canManage={canUpdateRole}
                onEdit={(pm) => setEditingPM(pm)}
              />
            )}

            {scope === 'workspace' && (
              <>
                <div style={{ height: 32 }} />
                <SectionHead
                  level="h2"
                  title="Roles disponibles"
                  sub="Presets del sistema (read-only) + roles custom de tu workspace. Cada rol es un set de permisos atómicos."
                  right={canManageRoles ? (
                    <Button variant="secondary" size="md" icon="plus" onClick={() => setEditingRole('new')}>
                      Nuevo rol custom
                    </Button>
                  ) : null}
                />
                <RolesTable
                  roles={roles}
                  permsCatalog={permsCatalog}
                  canManage={canManageRoles}
                  onEdit={(r) => setEditingRole(r)}
                />

                <div style={{ height: 32 }} />
                <SectionHead level="h2" title="Catálogo de permisos atómicos" sub={`${permsCatalog.length} permisos divididos en ${new Set(permsCatalog.map(p => p.category)).size} categorías. Cada rol es una combinación de estos.`} />
                <PermissionsCatalog perms={permsCatalog} />
              </>
            )}
          </>
        )}

        {editingMember && (
          <EditMemberRoleModal
            member={editingMember}
            roles={roles}
            permsCatalog={permsCatalog}
            onClose={() => setEditingMember(null)}
            onSaved={() => { setEditingMember(null); refresh(); }}
          />
        )}

        {showInvite && (
          <InviteMemberModal
            roles={roles}
            // Project-scoped invite when the user is currently on the
            // "Proyecto" tab. Backend creates a Guest workspace_member +
            // project_member; the invitee never sees other projects in this
            // workspace. Workspace tab keeps the legacy workspace-wide flow.
            projectId={scope === 'project' ? projectId : null}
            projectName={scope === 'project'
              ? ((window.PROJECTS || []).find((p) => p.id === projectId)?.name || projectId)
              : null}
            onClose={() => setShowInvite(false)}
            onInvited={() => { setShowInvite(false); refresh(); if (scope === 'project') refreshProject(projectId); }}
          />
        )}

        {editingRole && (
          <CustomRoleModal
            initial={editingRole === 'new' ? null : editingRole}
            permsCatalog={permsCatalog}
            onClose={() => setEditingRole(null)}
            onSaved={() => { setEditingRole(null); refresh(); }}
          />
        )}

        {editingPM && projectId && (
          <EditProjectMemberModal
            member={editingPM}
            projectId={projectId}
            roles={roles}
            permsCatalog={permsCatalog}
            onClose={() => setEditingPM(null)}
            onSaved={() => { setEditingPM(null); refreshProject(projectId); }}
          />
        )}
      </div>
    </div>
  );
}

function InvitesTable({ invites, onRevoked }) {
  const revoke = async (id) => {
    if (!confirm('¿Revocar esta invitación? El link enviado dejará de funcionar.')) return;
    try {
      await window.api('DELETE', `/api/workspace/invites/${id}`);
      onRevoked();
    } catch (e) { alert(e?.message || 'No se pudo revocar'); }
  };
  return (
    <div style={{ background: 'var(--bg-1)', border: '1px solid var(--border)', borderRadius: 10, overflow: 'hidden' }}>
      <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 13 }}>
        <thead>
          <tr style={{ background: 'var(--bg-2)', textAlign: 'left' }}>
            <th style={_thStyle}>Email invitado</th>
            <th style={_thStyle}>Rol</th>
            <th style={_thStyle}>Invitado por</th>
            <th style={_thStyle}>Expira</th>
            <th style={_thStyle}></th>
          </tr>
        </thead>
        <tbody>
          {invites.map((i) => {
            const expiresIn = Math.max(0, new Date(i.expiresAt).getTime() - Date.now());
            const days = Math.floor(expiresIn / (1000 * 60 * 60 * 24));
            return (
              <tr key={i.id} style={{ borderTop: '1px solid var(--border)' }}>
                <td style={_tdStyle}>
                  <span className="mono" style={{ color: 'var(--text-1)' }}>{i.email}</span>
                  {i.projectId && (
                    <div style={{ marginTop: 3, fontSize: 11, color: 'var(--text-2)' }}>
                      <span style={{
                        display: 'inline-block', padding: '1px 6px',
                        background: 'rgba(79,142,255,.10)', color: '#9DBDF5',
                        border: '1px solid rgba(79,142,255,.30)', borderRadius: 4,
                      }}>proyecto: {i.projectName || i.projectId}</span>
                    </div>
                  )}
                </td>
                <td style={_tdStyle}><RoleBadge name={i.roleName} isSystem={true} /></td>
                <td style={_tdStyle}><span style={{ color: 'var(--text-2)' }}>{i.invitedByName}</span></td>
                <td style={_tdStyle}>
                  <span style={{ color: days > 1 ? 'var(--text-2)' : 'var(--danger)', fontSize: 12 }}>
                    {days > 0 ? `en ${days} día${days === 1 ? '' : 's'}` : 'hoy'}
                  </span>
                </td>
                <td style={{ ..._tdStyle, textAlign: 'right' }}>
                  <Button variant="ghost" size="sm" icon="x" onClick={() => revoke(i.id)}>Revocar</Button>
                </td>
              </tr>
            );
          })}
        </tbody>
      </table>
    </div>
  );
}

function InviteMemberModal({ roles, onClose, onInvited, projectId, projectName }) {
  const isProjectScoped = !!projectId;
  const [email, setEmail] = React.useState('');
  const [roleId, setRoleId] = React.useState(() => {
    // Default to "Developer" preset if present, fall back to first non-Owner role
    const dev = roles.find((r) => r.name === 'Developer');
    if (dev) return dev.id;
    const nonOwner = roles.find((r) => r.name !== 'Owner');
    return (nonOwner ?? roles[0])?.id || '';
  });
  const [submitting, setSubmitting] = React.useState(false);
  const [error, setError] = React.useState(null);

  const targetRole = roles.find(r => r.id === roleId);

  const submit = async () => {
    setError(null);
    setSubmitting(true);
    try {
      const body = { email: email.trim().toLowerCase(), roleId };
      if (projectId) body.projectId = projectId;
      await window.api('POST', '/api/workspace/invites', body);
      onInvited();
    } catch (e) {
      if (e.status === 409) {
        setError(projectId
          ? 'Ese email ya es miembro de este proyecto.'
          : 'Ese email ya es miembro del workspace.');
      } else {
        setError(e?.message || 'No se pudo crear la invitación');
      }
      setSubmitting(false);
    }
  };

  const valid = email.trim() && /@/.test(email) && roleId;

  return (
    <Modal
      title={isProjectScoped ? `Invitar al proyecto · ${projectName || projectId}` : 'Invitar al workspace'}
      onClose={onClose}
      width={520}
      footer={<>
        <Button variant="ghost" onClick={onClose} disabled={submitting}>Cancelar</Button>
        <Button variant="primary" onClick={submit} disabled={!valid || submitting}>
          {submitting ? 'Enviando…' : 'Enviar invitación'}
        </Button>
      </>}
    >
      {isProjectScoped && (
        <div style={{
          marginBottom: 14, padding: '10px 12px',
          background: 'rgba(79,142,255,.08)', border: '1px solid rgba(79,142,255,.30)',
          borderRadius: 6, color: '#9DBDF5', fontSize: 12, lineHeight: 1.5,
        }}>
          Invitación scoped al proyecto <strong>{projectName || projectId}</strong>. El invitado entra como <strong>Guest</strong> al workspace y solo ve este proyecto — no aparece como colaborador del workspace ni de otros proyectos.
        </div>
      )}
      {error && (
        <div style={{
          marginBottom: 12, padding: '8px 12px',
          background: 'rgba(239,68,68,.08)', border: '1px solid rgba(239,68,68,.30)',
          borderRadius: 6, color: 'var(--danger)', fontSize: 12,
        }}>{error}</div>
      )}
      <label style={{ display: 'block', marginBottom: 14 }}>
        <div style={{ fontSize: 11, fontWeight: 600, color: 'var(--text-2)', textTransform: 'uppercase', marginBottom: 6 }}>Email del colaborador</div>
        <input
          type="email"
          autoFocus
          value={email}
          onChange={(e) => setEmail(e.target.value)}
          placeholder="pancho@empresa.com"
          maxLength={254}
          style={{
            width: '100%', padding: '10px 12px',
            background: 'var(--bg-1)', border: '1px solid var(--border)',
            borderRadius: 6, color: 'var(--text-0)', fontSize: 14, outline: 'none',
          }}
        />
      </label>
      <label style={{ display: 'block', marginBottom: 14 }}>
        <div style={{ fontSize: 11, fontWeight: 600, color: 'var(--text-2)', textTransform: 'uppercase', marginBottom: 6 }}>Rol</div>
        <select
          value={roleId}
          onChange={(e) => setRoleId(e.target.value)}
          style={{
            width: '100%', padding: '10px 12px', background: 'var(--bg-1)',
            border: '1px solid var(--border)', borderRadius: 6, color: 'var(--text-0)',
            fontSize: 14,
          }}
        >
          {roles.filter(r => r.name !== 'Owner').map((r) => (
            <option key={r.id} value={r.id}>
              {r.name} ({r.permissions.length} permisos)
            </option>
          ))}
        </select>
      </label>
      {targetRole && (
        <div style={{ background: 'var(--bg-2)', borderRadius: 6, padding: 12, fontSize: 12, color: 'var(--text-1)' }}>
          {targetRole.description || 'Rol sin descripción.'}
        </div>
      )}
      <div style={{ marginTop: 14, fontSize: 11, color: 'var(--text-2)', lineHeight: 1.5 }}>
        Le enviaremos un email con un enlace de invitación. El enlace expira en 7 días.
        Si no tiene cuenta todavía, puede crearla desde ese mismo enlace.
      </div>
    </Modal>
  );
}

function MembersTable({ members, roles, canUpdateRole, onEdit }) {
  if (!members.length) {
    return <_CollabEmptyState title="Sin miembros" sub="No hay miembros en este workspace." />;
  }
  return (
    <div style={{ background: 'var(--bg-1)', border: '1px solid var(--border)', borderRadius: 10, overflow: 'hidden' }}>
      <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 13 }}>
        <thead>
          <tr style={{ background: 'var(--bg-2)', textAlign: 'left' }}>
            <th style={_thStyle}>Miembro</th>
            <th style={_thStyle}>Email</th>
            <th style={_thStyle}>Rol</th>
            <th style={_thStyle}>Permisos efectivos</th>
            <th style={_thStyle}>Joined</th>
            <th style={_thStyle}></th>
          </tr>
        </thead>
        <tbody>
          {members.map((m) => (
            <tr key={m.id} style={{ borderTop: '1px solid var(--border)' }}>
              <td style={_tdStyle}>
                <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
                  <_CollabAvatar name={m.displayName} email={m.email} src={m.avatarUrl} size={28} />
                  <div>
                    <div style={{ fontWeight: 600, color: 'var(--text-0)' }}>
                      {m.displayName}
                      {m.isOwner && <span style={_ownerBadgeStyle}>OWNER</span>}
                    </div>
                  </div>
                </div>
              </td>
              <td style={_tdStyle}><span className="mono" style={{ color: 'var(--text-2)' }}>{m.email}</span></td>
              <td style={_tdStyle}>
                <RoleBadge name={m.role.name} isSystem={m.role.isSystem} />
              </td>
              <td style={_tdStyle}>
                <span style={{ color: 'var(--text-2)' }}>
                  {m.permissions.length} de {roles.find(r => r.id === m.role.id)?.permissions.length ?? '?'} del rol
                </span>
                {(m.customPermissions.adds.length > 0 || m.customPermissions.removes.length > 0) && (
                  <span style={{ marginLeft: 8, padding: '2px 6px', background: 'var(--bg-2)', borderRadius: 4, fontSize: 11, color: 'var(--text-2)' }}>
                    custom: +{m.customPermissions.adds.length} / −{m.customPermissions.removes.length}
                  </span>
                )}
              </td>
              <td style={_tdStyle}>
                <span style={{ color: 'var(--text-2)' }}>
                  {new Date(m.joinedAt).toLocaleDateString('es-MX', { year: 'numeric', month: 'short', day: 'numeric' })}
                </span>
              </td>
              <td style={{ ..._tdStyle, textAlign: 'right' }}>
                {canUpdateRole && !m.isOwner && (
                  <Button variant="ghost" size="sm" icon="settings" onClick={() => onEdit(m)}>Editar</Button>
                )}
              </td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

function RolesTable({ roles, permsCatalog, canManage, onEdit }) {
  const totalPerms = permsCatalog.length;
  const [busyId, setBusyId] = React.useState(null);

  const remove = async (r) => {
    if (!confirm(`¿Borrar el rol custom "${r.name}"?\n\nSi todavía hay miembros con este rol, la operación va a fallar — primero reasígnalos.`)) return;
    setBusyId(r.id);
    try {
      await window.api('DELETE', `/api/workspace/roles/${r.id}`);
      // Refresh handled by parent via realtime workspace.role.changed event.
    } catch (e) {
      alert(e?.message || 'No se pudo borrar');
    } finally {
      setBusyId(null);
    }
  };

  return (
    <div style={{ background: 'var(--bg-1)', border: '1px solid var(--border)', borderRadius: 10, overflow: 'hidden' }}>
      <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 13 }}>
        <thead>
          <tr style={{ background: 'var(--bg-2)', textAlign: 'left' }}>
            <th style={_thStyle}>Rol</th>
            <th style={_thStyle}>Tipo</th>
            <th style={_thStyle}>Cobertura</th>
            <th style={_thStyle}>Descripción</th>
            <th style={_thStyle}></th>
          </tr>
        </thead>
        <tbody>
          {roles.map((r) => (
            <tr key={r.id} style={{ borderTop: '1px solid var(--border)' }}>
              <td style={_tdStyle}><RoleBadge name={r.name} isSystem={r.isSystem} /></td>
              <td style={_tdStyle}>
                <span style={{ color: 'var(--text-2)' }}>{r.isSystem ? 'Preset del sistema' : 'Custom del workspace'}</span>
              </td>
              <td style={_tdStyle}>
                <PermissionBar count={r.permissions.length} total={totalPerms} />
              </td>
              <td style={_tdStyle}>
                <span style={{ color: 'var(--text-1)' }}>{r.description || '—'}</span>
              </td>
              <td style={{ ..._tdStyle, textAlign: 'right' }}>
                {canManage && !r.isSystem && (
                  <div style={{ display: 'inline-flex', gap: 4 }}>
                    <Button variant="ghost" size="sm" icon="settings" onClick={() => onEdit(r)} disabled={busyId === r.id}>
                      Editar
                    </Button>
                    <Button variant="ghost" size="sm" icon="trash-2" onClick={() => remove(r)} disabled={busyId === r.id}>
                      Borrar
                    </Button>
                  </div>
                )}
              </td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

// Create or edit a custom role. `initial=null` → create mode; otherwise the
// modal pre-fills with the existing role and PATCHes on save. Permissions
// are picked via category-grouped checkboxes; the user must include at
// least one to satisfy the backend constraint.
function CustomRoleModal({ initial, permsCatalog, onClose, onSaved }) {
  const isEdit = !!initial;
  const [name, setName] = React.useState(initial?.name || '');
  const [description, setDescription] = React.useState(initial?.description || '');
  const [selected, setSelected] = React.useState(() => new Set(initial?.permissions || []));
  const [saving, setSaving] = React.useState(false);
  const [error, setError] = React.useState(null);

  const byCategory = React.useMemo(() => {
    const out = {};
    for (const p of permsCatalog) {
      if (!out[p.category]) out[p.category] = [];
      out[p.category].push(p);
    }
    return out;
  }, [permsCatalog]);

  const togglePerm = (id) => {
    setSelected((prev) => {
      const next = new Set(prev);
      if (next.has(id)) next.delete(id); else next.add(id);
      return next;
    });
  };

  const toggleCategory = (cat) => {
    const list = byCategory[cat] || [];
    const allOn = list.every((p) => selected.has(p.id));
    setSelected((prev) => {
      const next = new Set(prev);
      for (const p of list) {
        if (allOn) next.delete(p.id);
        else next.add(p.id);
      }
      return next;
    });
  };

  const valid = name.trim().length >= 1 && selected.size >= 1;

  const save = async () => {
    setError(null);
    setSaving(true);
    try {
      if (isEdit) {
        await window.api('PATCH', `/api/workspace/roles/${initial.id}`, {
          name: name.trim(),
          description: description.trim() || null,
          permissions: [...selected],
        });
      } else {
        await window.api('POST', '/api/workspace/roles', {
          name: name.trim(),
          description: description.trim() || null,
          permissions: [...selected],
        });
      }
      onSaved();
    } catch (e) {
      setError(e?.message || 'No se pudo guardar');
      setSaving(false);
    }
  };

  return (
    <Modal
      title={isEdit ? `Editar rol: ${initial.name}` : 'Crear rol custom'}
      onClose={onClose}
      width={680}
      footer={<>
        <Button variant="ghost" onClick={onClose} disabled={saving}>Cancelar</Button>
        <Button variant="primary" onClick={save} disabled={!valid || saving}>
          {saving ? 'Guardando…' : (isEdit ? 'Guardar cambios' : 'Crear rol')}
        </Button>
      </>}
    >
      {error && (
        <div style={{
          marginBottom: 12, padding: '8px 12px',
          background: 'rgba(239,68,68,.08)', border: '1px solid rgba(239,68,68,.30)',
          borderRadius: 6, color: 'var(--danger)', fontSize: 12,
        }}>{error}</div>
      )}
      <label style={{ display: 'block', marginBottom: 14 }}>
        <div style={{ fontSize: 11, fontWeight: 600, color: 'var(--text-2)', textTransform: 'uppercase', marginBottom: 6 }}>Nombre</div>
        <input
          autoFocus
          value={name}
          onChange={(e) => setName(e.target.value)}
          placeholder="Ej: Editor de docs"
          maxLength={60}
          style={{
            width: '100%', padding: '10px 12px',
            background: 'var(--bg-1)', border: '1px solid var(--border)',
            borderRadius: 6, color: 'var(--text-0)', fontSize: 14, outline: 'none',
          }}
        />
      </label>
      <label style={{ display: 'block', marginBottom: 14 }}>
        <div style={{ fontSize: 11, fontWeight: 600, color: 'var(--text-2)', textTransform: 'uppercase', marginBottom: 6 }}>Descripción <span style={{ color: 'var(--text-3)', fontWeight: 400 }}>(opcional)</span></div>
        <input
          value={description}
          onChange={(e) => setDescription(e.target.value)}
          placeholder="Para qué sirve este rol"
          maxLength={500}
          style={{
            width: '100%', padding: '10px 12px',
            background: 'var(--bg-1)', border: '1px solid var(--border)',
            borderRadius: 6, color: 'var(--text-0)', fontSize: 14, outline: 'none',
          }}
        />
      </label>

      <div style={{ fontSize: 11, fontWeight: 600, color: 'var(--text-2)', textTransform: 'uppercase', marginBottom: 6 }}>
        Permisos <span style={{ color: 'var(--text-3)', fontWeight: 400 }}>· {selected.size} seleccionado{selected.size === 1 ? '' : 's'}</span>
      </div>
      <div style={{
        border: '1px solid var(--border)', borderRadius: 8,
        maxHeight: 360, overflowY: 'auto',
      }}>
        {Object.entries(byCategory).map(([cat, list], idx) => {
          const onCount = list.filter((p) => selected.has(p.id)).length;
          const allOn = onCount === list.length;
          const someOn = onCount > 0 && !allOn;
          return (
            <div key={cat} style={{ borderTop: idx === 0 ? 'none' : '1px solid var(--border)' }}>
              <div style={{
                padding: '8px 12px', background: 'var(--bg-2)',
                display: 'flex', alignItems: 'center', justifyContent: 'space-between',
                cursor: 'pointer', userSelect: 'none',
              }} onClick={() => toggleCategory(cat)}>
                <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                  <input
                    type="checkbox"
                    readOnly
                    checked={allOn}
                    ref={(el) => { if (el) el.indeterminate = someOn; }}
                  />
                  <span style={{ fontSize: 12, fontWeight: 600, color: 'var(--text-1)' }}>
                    {_CATEGORY_LABEL[cat] || cat}
                  </span>
                </div>
                <span style={{ fontSize: 11, color: 'var(--text-2)' }}>{onCount}/{list.length}</span>
              </div>
              <div>
                {list.map((p) => (
                  <label key={p.id} style={{
                    display: 'flex', alignItems: 'flex-start', gap: 10,
                    padding: '8px 14px 8px 32px', cursor: 'pointer',
                    borderTop: '1px solid var(--border)',
                  }}>
                    <input
                      type="checkbox"
                      checked={selected.has(p.id)}
                      onChange={() => togglePerm(p.id)}
                      style={{ marginTop: 2 }}
                    />
                    <div style={{ flex: 1 }}>
                      <span className="mono" style={{ fontSize: 11, color: 'var(--accent)' }}>{p.id}</span>
                      <div style={{ fontSize: 12, color: 'var(--text-1)' }}>{p.description}</div>
                    </div>
                  </label>
                ))}
              </div>
            </div>
          );
        })}
      </div>
    </Modal>
  );
}

function PermissionBar({ count, total }) {
  const pct = total > 0 ? Math.round((count / total) * 100) : 0;
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 8, minWidth: 180 }}>
      <div style={{ flex: 1, height: 6, background: 'var(--bg-2)', borderRadius: 3, overflow: 'hidden' }}>
        <div style={{ width: pct + '%', height: '100%', background: 'var(--accent)' }} />
      </div>
      <span className="mono" style={{ color: 'var(--text-2)', fontSize: 11, minWidth: 56, textAlign: 'right' }}>{count}/{total}</span>
    </div>
  );
}

function PermissionsCatalog({ perms }) {
  const byCategory = {};
  for (const p of perms) {
    if (!byCategory[p.category]) byCategory[p.category] = [];
    byCategory[p.category].push(p);
  }
  return (
    <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(320px, 1fr))', gap: 12 }}>
      {Object.entries(byCategory).map(([cat, list]) => (
        <div key={cat} style={{
          background: 'var(--bg-1)', border: '1px solid var(--border)',
          borderRadius: 10, padding: 14,
        }}>
          <div style={{ fontSize: 11, fontWeight: 700, color: 'var(--text-2)', textTransform: 'uppercase', letterSpacing: 0.5, marginBottom: 8 }}>
            {_CATEGORY_LABEL[cat] || cat} <span style={{ color: 'var(--text-3)' }}>· {list.length}</span>
          </div>
          {list.map((p) => (
            <div key={p.id} style={{ marginBottom: 6 }}>
              <span className="mono" style={{ fontSize: 11, color: 'var(--accent)' }}>{p.id}</span>
              <div style={{ fontSize: 12, color: 'var(--text-1)' }}>{p.description}</div>
            </div>
          ))}
        </div>
      ))}
    </div>
  );
}

function RoleBadge({ name, isSystem }) {
  return (
    <span style={{
      display: 'inline-flex', alignItems: 'center', gap: 6,
      padding: '3px 8px', borderRadius: 999,
      background: isSystem ? 'var(--bg-2)' : 'rgba(79,142,255,.12)',
      border: '1px solid ' + (isSystem ? 'var(--border)' : 'rgba(79,142,255,.30)'),
      fontSize: 12, fontWeight: 600,
      color: isSystem ? 'var(--text-1)' : 'var(--accent)',
    }}>
      {!isSystem && <Icon name="sparkles" size={11} />}
      {name}
    </span>
  );
}

function _CollabAvatar({ name, email, src, size = 32 }) {
  // Prefer first letters of the displayName (up to 2 words, e.g. "Genaro
  // Plascencia" → "GP"). If no name, fall back to the first letter of the
  // email's local part. Only show "?" if literally everything is empty —
  // shouldn't happen since signup requires both.
  const initials = (() => {
    const fromName = String(name || '')
      .split(/\s+/)
      .map((s) => s.trim()[0])
      .filter(Boolean)
      .slice(0, 2)
      .join('')
      .toUpperCase();
    if (fromName) return fromName;
    const fromEmail = String(email || '').trim().charAt(0).toUpperCase();
    return fromEmail || '?';
  })();
  // Deterministic background color based on the seed so each user keeps the
  // same avatar across renders. Picks from a curated palette that reads well
  // on the dark theme.
  const seed = String(name || email || '').toLowerCase();
  const palette = ['#4F8EFF', '#10B981', '#F59E0B', '#A855F7', '#EC4899', '#06B6D4', '#F97316', '#8B5CF6'];
  let h = 0;
  for (let i = 0; i < seed.length; i++) h = (h * 31 + seed.charCodeAt(i)) >>> 0;
  const bg = palette[h % palette.length];
  if (src) {
    return <img src={src} alt={name || email || ''} style={{ width: size, height: size, borderRadius: '50%' }} />;
  }
  return (
    <div style={{
      width: size, height: size, borderRadius: '50%',
      background: bg, color: '#0F1115',
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      fontSize: size * 0.42, fontWeight: 700,
      flexShrink: 0,
    }}>
      {initials}
    </div>
  );
}

function _CollabEmptyState({ title, sub }) {
  return (
    <div style={{ padding: 60, textAlign: 'center', background: 'var(--bg-1)', border: '1px solid var(--border)', borderRadius: 10 }}>
      <div style={{ fontSize: 16, fontWeight: 600, color: 'var(--text-0)', marginBottom: 4 }}>{title}</div>
      <div style={{ fontSize: 13, color: 'var(--text-2)' }}>{sub}</div>
    </div>
  );
}

// Edit a member's role + customPermissions overrides. Two PATCHes are sent
// only if their respective inputs changed (role select; adds/removes sets).
// The effective permission set is computed live so the user sees exactly
// what the member will end up with.
function EditMemberRoleModal({ member, roles, permsCatalog, onClose, onSaved }) {
  const [newRoleId, setNewRoleId] = React.useState(member.role.id);
  const [adds, setAdds] = React.useState(() => new Set(member.customPermissions.adds));
  const [removes, setRemoves] = React.useState(() => new Set(member.customPermissions.removes));
  const [saving, setSaving] = React.useState(false);
  const [error, setError] = React.useState(null);
  const [showOverrides, setShowOverrides] = React.useState(
    member.customPermissions.adds.length + member.customPermissions.removes.length > 0
  );

  const targetRole = roles.find(r => r.id === newRoleId);
  const baseline = React.useMemo(
    () => new Set(targetRole?.permissions || []),
    [targetRole],
  );

  // Effective preview: baseline ∪ adds \ removes — same logic as the engine.
  const effective = React.useMemo(() => {
    const out = new Set(baseline);
    for (const a of adds) out.add(a);
    for (const r of removes) out.delete(r);
    return out;
  }, [baseline, adds, removes]);

  const byCategory = React.useMemo(() => {
    const out = {};
    for (const p of permsCatalog) {
      if (!out[p.category]) out[p.category] = [];
      out[p.category].push(p);
    }
    return out;
  }, [permsCatalog]);

  // Three-state toggle on a permission relative to the role's baseline:
  //   - in baseline + not in removes  → "default" (role gives it)
  //   - in baseline + in removes      → "explicitly removed"
  //   - not in baseline + in adds     → "explicitly added"
  //   - not in baseline + not in adds → "default" (role doesn't give it)
  // Click cycles: default ↔ override (added if missing from baseline,
  // removed if present in baseline).
  const togglePerm = (id) => {
    const inBase = baseline.has(id);
    if (inBase) {
      setRemoves((prev) => {
        const next = new Set(prev);
        if (next.has(id)) next.delete(id); else next.add(id);
        return next;
      });
      setAdds((prev) => { const next = new Set(prev); next.delete(id); return next; });
    } else {
      setAdds((prev) => {
        const next = new Set(prev);
        if (next.has(id)) next.delete(id); else next.add(id);
        return next;
      });
      setRemoves((prev) => { const next = new Set(prev); next.delete(id); return next; });
    }
  };

  const resetOverrides = () => {
    setAdds(new Set());
    setRemoves(new Set());
  };

  const save = async () => {
    setSaving(true);
    setError(null);
    try {
      const tasks = [];
      if (newRoleId !== member.role.id) {
        tasks.push(window.api('PATCH', `/api/workspace/members/${member.userId}`, { roleId: newRoleId }));
      }
      const baselineAdds = new Set(member.customPermissions.adds);
      const baselineRemoves = new Set(member.customPermissions.removes);
      const sameSet = (a, b) => a.size === b.size && [...a].every((x) => b.has(x));
      const overridesChanged = !sameSet(adds, baselineAdds) || !sameSet(removes, baselineRemoves);
      if (overridesChanged) {
        tasks.push(window.api('PATCH', `/api/workspace/members/${member.userId}/custom-permissions`, {
          adds: [...adds],
          removes: [...removes],
        }));
      }
      if (tasks.length === 0) { onClose(); return; }
      // Run sequentially: backend invalidates the cache after each, and the
      // second PATCH benefits from the fresh role context.
      for (const t of tasks) await t;
      onSaved();
    } catch (e) {
      setError(e?.message || 'No se pudo guardar');
      setSaving(false);
    }
  };

  const dirty = newRoleId !== member.role.id ||
    adds.size !== member.customPermissions.adds.length ||
    removes.size !== member.customPermissions.removes.length ||
    [...adds].some((a) => !member.customPermissions.adds.includes(a)) ||
    [...removes].some((r) => !member.customPermissions.removes.includes(r));

  return (
    <Modal
      title={`Editar rol de ${member.displayName}`}
      onClose={onClose}
      width={680}
      footer={<>
        <Button variant="ghost" onClick={onClose} disabled={saving}>Cancelar</Button>
        <Button variant="primary" onClick={save} disabled={saving || !dirty}>
          {saving ? 'Guardando…' : 'Guardar cambios'}
        </Button>
      </>}
    >
      {error && (
        <div style={{
          marginBottom: 12, padding: '8px 12px',
          background: 'rgba(239,68,68,.08)', border: '1px solid rgba(239,68,68,.30)',
          borderRadius: 6, color: 'var(--danger)', fontSize: 12,
        }}>{error}</div>
      )}
      <label style={{ display: 'block', marginBottom: 14 }}>
        <div style={{ fontSize: 11, fontWeight: 600, color: 'var(--text-2)', textTransform: 'uppercase', marginBottom: 6 }}>Rol</div>
        <select
          value={newRoleId}
          onChange={(e) => setNewRoleId(e.target.value)}
          style={{
            width: '100%', padding: '10px 12px', background: 'var(--bg-1)',
            border: '1px solid var(--border)', borderRadius: 6, color: 'var(--text-0)',
            fontSize: 14,
          }}
        >
          {roles.map((r) => (
            <option key={r.id} value={r.id}>
              {r.name} ({r.permissions.length} permisos) {r.isSystem ? '' : '· custom'}
            </option>
          ))}
        </select>
      </label>
      {targetRole && (
        <div style={{ background: 'var(--bg-2)', borderRadius: 6, padding: 12, fontSize: 12, marginBottom: 14 }}>
          <div style={{ color: 'var(--text-1)', marginBottom: 4 }}>{targetRole.description || 'Sin descripción.'}</div>
          <div style={{ color: 'var(--text-2)' }}>
            <strong style={{ color: 'var(--text-1)' }}>{effective.size}</strong> permisos efectivos
            {(adds.size > 0 || removes.size > 0) && (
              <span style={{ marginLeft: 8 }}>
                (rol: {baseline.size}
                {adds.size > 0 && <span style={{ color: '#10b981' }}> +{adds.size}</span>}
                {removes.size > 0 && <span style={{ color: 'var(--danger)' }}> −{removes.size}</span>}
                )
              </span>
            )}
          </div>
        </div>
      )}

      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 8 }}>
        <button className="btn-reset" onClick={() => setShowOverrides((v) => !v)} style={{
          fontSize: 11, fontWeight: 600, color: 'var(--text-2)', textTransform: 'uppercase',
          letterSpacing: 0.4, display: 'flex', alignItems: 'center', gap: 6, cursor: 'pointer',
        }}>
          <Icon name={showOverrides ? 'chevron-down' : 'chevron-right'} size={12} />
          Overrides custom <span style={{ color: 'var(--text-3)', fontWeight: 400 }}>· {adds.size + removes.size}</span>
        </button>
        {(adds.size > 0 || removes.size > 0) && (
          <Button variant="ghost" size="sm" onClick={resetOverrides}>Limpiar overrides</Button>
        )}
      </div>

      {showOverrides && (
        <>
          <div style={{ fontSize: 11, color: 'var(--text-2)', marginBottom: 8, lineHeight: 1.5 }}>
            Marca un permiso fuera del rol para <strong style={{ color: '#10b981' }}>agregárselo</strong>; desmárcalo del rol para <strong style={{ color: 'var(--danger)' }}>quitárselo</strong>. Los overrides se aplican sobre cualquier rol que esté activo.
          </div>
          <div style={{
            border: '1px solid var(--border)', borderRadius: 8,
            maxHeight: 320, overflowY: 'auto',
          }}>
            {Object.entries(byCategory).map(([cat, list], idx) => (
              <div key={cat} style={{ borderTop: idx === 0 ? 'none' : '1px solid var(--border)' }}>
                <div style={{ padding: '8px 12px', background: 'var(--bg-2)', fontSize: 12, fontWeight: 600, color: 'var(--text-1)' }}>
                  {_CATEGORY_LABEL[cat] || cat}
                </div>
                {list.map((p) => {
                  const inBase = baseline.has(p.id);
                  const isAdded = adds.has(p.id);
                  const isRemoved = removes.has(p.id);
                  const has = effective.has(p.id);
                  let badge = null;
                  if (isAdded) badge = <span style={{ marginLeft: 6, fontSize: 10, padding: '1px 5px', borderRadius: 3, background: 'rgba(16,185,129,.18)', color: '#10b981', fontWeight: 700 }}>+ ADD</span>;
                  else if (isRemoved) badge = <span style={{ marginLeft: 6, fontSize: 10, padding: '1px 5px', borderRadius: 3, background: 'rgba(239,68,68,.18)', color: 'var(--danger)', fontWeight: 700 }}>− REMOVE</span>;
                  else if (inBase) badge = <span style={{ marginLeft: 6, fontSize: 10, color: 'var(--text-3)' }}>del rol</span>;
                  return (
                    <label key={p.id} style={{
                      display: 'flex', alignItems: 'flex-start', gap: 10,
                      padding: '8px 14px', cursor: 'pointer',
                      borderTop: '1px solid var(--border)',
                      opacity: has ? 1 : 0.55,
                    }}>
                      <input
                        type="checkbox"
                        checked={has}
                        onChange={() => togglePerm(p.id)}
                        style={{ marginTop: 2 }}
                      />
                      <div style={{ flex: 1 }}>
                        <span className="mono" style={{ fontSize: 11, color: 'var(--accent)' }}>{p.id}</span>
                        {badge}
                        <div style={{ fontSize: 12, color: 'var(--text-1)' }}>{p.description}</div>
                      </div>
                    </label>
                  );
                })}
              </div>
            ))}
          </div>
        </>
      )}
    </Modal>
  );
}

const _thStyle = {
  padding: '10px 14px',
  fontSize: 11,
  fontWeight: 700,
  color: 'var(--text-2)',
  textTransform: 'uppercase',
  letterSpacing: 0.4,
};
const _tdStyle = { padding: '14px', verticalAlign: 'middle' };
const _ownerBadgeStyle = {
  marginLeft: 8, padding: '2px 6px',
  background: 'rgba(245,158,11,.14)', color: '#f59e0b',
  borderRadius: 4, fontSize: 10, fontWeight: 700, letterSpacing: 0.5,
};

// Toggle between workspace-wide membership view and per-project view.
// Persists the choice in localStorage so a manager who lives in one of
// these scopes doesn't have to flip the switch on every visit.
function ScopeTabs({ scope, setScope, projectId, setProjectId }) {
  const projects = window.PROJECTS || [];
  const tabBtn = (id, label) => {
    const active = scope === id;
    return (
      <button
        key={id}
        className="btn-reset"
        onClick={() => {
          setScope(id);
          try { localStorage.setItem('ws.colab.scope', id); } catch {}
        }}
        style={{
          padding: '6px 14px', fontSize: 13, fontWeight: 600,
          color: active ? 'var(--text-0)' : 'var(--text-2)',
          background: active ? 'var(--bg-1)' : 'transparent',
          border: '1px solid ' + (active ? 'var(--border-strong)' : 'transparent'),
          borderRadius: 8, cursor: 'pointer',
        }}
      >
        {label}
      </button>
    );
  };
  return (
    <div style={{
      display: 'flex', alignItems: 'center', justifyContent: 'space-between',
      gap: 12, padding: 4, marginBottom: 16,
      background: 'var(--bg-2)', border: '1px solid var(--border)', borderRadius: 10,
    }}>
      <div style={{ display: 'flex', gap: 4 }}>
        {tabBtn('workspace', 'Workspace')}
        {tabBtn('project', 'Por proyecto')}
      </div>
      {scope === 'project' && (
        <div style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '0 8px' }}>
          <span style={{ fontSize: 12, color: 'var(--text-2)' }}>Proyecto:</span>
          <select
            value={projectId || ''}
            onChange={(e) => setProjectId(e.target.value)}
            style={{
              padding: '6px 10px', background: 'var(--bg-1)',
              border: '1px solid var(--border)', borderRadius: 6, color: 'var(--text-0)',
              fontSize: 13,
            }}
          >
            {projects.length === 0 && <option value="">(sin proyectos)</option>}
            {projects.map((p) => (
              <option key={p.id} value={p.id}>{p.name}</option>
            ))}
          </select>
        </div>
      )}
    </div>
  );
}

// Per-project member table — same shape as MembersTable but adds the
// "inherit / override" indicator + an "excluded" toggle. Rows always come
// from the workspace roster; ProjectMember rows are an overlay.
function ProjectMembersTable({ projectId, members, roles, canManage, onEdit }) {
  if (!projectId) {
    return <_CollabEmptyState title="Elige un proyecto" sub="Selecciona un proyecto del selector de arriba para gestionar sus miembros." />;
  }
  if (!members.length) {
    return <_CollabEmptyState title="Sin miembros" sub="Este proyecto no tiene miembros aún. Empieza invitando colaboradores al workspace." />;
  }
  return (
    <div style={{ background: 'var(--bg-1)', border: '1px solid var(--border)', borderRadius: 10, overflow: 'hidden' }}>
      <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 13 }}>
        <thead>
          <tr style={{ background: 'var(--bg-2)', textAlign: 'left' }}>
            <th style={_thStyle}>Miembro</th>
            <th style={_thStyle}>Email</th>
            <th style={_thStyle}>Rol en el proyecto</th>
            <th style={_thStyle}>Override</th>
            <th style={_thStyle}>Permisos</th>
            <th style={_thStyle}></th>
          </tr>
        </thead>
        <tbody>
          {members.map((m) => {
            const hasOverride = m.overrideRoleId || m.customPermissions.adds.length > 0 || m.customPermissions.removes.length > 0 || m.excluded;
            return (
              <tr key={m.userId} style={{
                borderTop: '1px solid var(--border)',
                opacity: m.excluded ? 0.5 : 1,
              }}>
                <td style={_tdStyle}>
                  <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
                    <_CollabAvatar name={m.displayName} email={m.email} src={m.avatarUrl} size={28} />
                    <div style={{ fontWeight: 600, color: 'var(--text-0)' }}>
                      {m.displayName}
                      {m.excluded && (
                        <span style={{ marginLeft: 8, padding: '2px 6px', background: 'rgba(239,68,68,.14)', color: 'var(--danger)', borderRadius: 4, fontSize: 10, fontWeight: 700, letterSpacing: 0.5 }}>EXCLUIDO</span>
                      )}
                    </div>
                  </div>
                </td>
                <td style={_tdStyle}><span className="mono" style={{ color: 'var(--text-2)' }}>{m.email}</span></td>
                <td style={_tdStyle}>
                  <RoleBadge name={m.effectiveRole.name} isSystem={m.effectiveRole.isSystem} />
                </td>
                <td style={_tdStyle}>
                  {hasOverride ? (
                    <span style={{ display: 'inline-flex', alignItems: 'center', gap: 6, padding: '3px 8px', borderRadius: 999, background: 'rgba(245,158,11,.14)', color: '#f59e0b', fontSize: 11, fontWeight: 600 }}>
                      <Icon name="zap" size={11} /> Override
                    </span>
                  ) : (
                    <span style={{ color: 'var(--text-3)', fontSize: 12 }}>hereda del workspace</span>
                  )}
                  {(m.customPermissions.adds.length > 0 || m.customPermissions.removes.length > 0) && (
                    <span style={{ marginLeft: 8, padding: '2px 6px', background: 'var(--bg-2)', borderRadius: 4, fontSize: 11, color: 'var(--text-2)' }}>
                      +{m.customPermissions.adds.length} / −{m.customPermissions.removes.length}
                    </span>
                  )}
                </td>
                <td style={_tdStyle}>
                  <span style={{ color: 'var(--text-2)' }}>{m.permissions.length} efectivos</span>
                </td>
                <td style={{ ..._tdStyle, textAlign: 'right' }}>
                  {canManage && (
                    <Button variant="ghost" size="sm" icon="settings" onClick={() => onEdit(m)}>
                      Configurar
                    </Button>
                  )}
                </td>
              </tr>
            );
          })}
        </tbody>
      </table>
    </div>
  );
}

// Modal for editing a member's per-project override. Three things in one:
//   - excluded toggle (full ban from this project)
//   - role override (pick a different role just for this project)
//   - customPermissions adds/removes layered on top
// "Resetear a workspace" deletes the override row entirely.
function EditProjectMemberModal({ member, projectId, roles, permsCatalog, onClose, onSaved }) {
  const [excluded, setExcluded] = React.useState(member.excluded);
  const [overrideRoleId, setOverrideRoleId] = React.useState(member.overrideRoleId || '');
  const [adds, setAdds] = React.useState(() => new Set(member.customPermissions.adds));
  const [removes, setRemoves] = React.useState(() => new Set(member.customPermissions.removes));
  const [saving, setSaving] = React.useState(false);
  const [error, setError] = React.useState(null);
  const [showOverrides, setShowOverrides] = React.useState(
    member.customPermissions.adds.length + member.customPermissions.removes.length > 0
  );

  const effectiveRole = overrideRoleId ? roles.find(r => r.id === overrideRoleId) : null;
  // When no role override is set, baseline = workspace-default's permissions.
  // We don't have those directly here so we derive from member.permissions
  // minus the current overrides. Cleaner approach: just show the
  // effectiveRole (override or current effective)'s permission set.
  const baseline = React.useMemo(() => {
    if (effectiveRole) return new Set(effectiveRole.permissions);
    // No override role → baseline is whatever the member would get from
    // workspace defaults. Approximate with current effective minus overrides.
    return new Set(
      member.permissions
        .filter((p) => !member.customPermissions.adds.includes(p))
        .concat(member.customPermissions.removes),
    );
  }, [effectiveRole, member]);

  const effective = React.useMemo(() => {
    if (excluded) return new Set();
    const out = new Set(baseline);
    for (const a of adds) out.add(a);
    for (const r of removes) out.delete(r);
    return out;
  }, [baseline, adds, removes, excluded]);

  const byCategory = React.useMemo(() => {
    const out = {};
    for (const p of permsCatalog) {
      if (!out[p.category]) out[p.category] = [];
      out[p.category].push(p);
    }
    return out;
  }, [permsCatalog]);

  const togglePerm = (id) => {
    const inBase = baseline.has(id);
    if (inBase) {
      setRemoves((prev) => { const next = new Set(prev); if (next.has(id)) next.delete(id); else next.add(id); return next; });
      setAdds((prev) => { const next = new Set(prev); next.delete(id); return next; });
    } else {
      setAdds((prev) => { const next = new Set(prev); if (next.has(id)) next.delete(id); else next.add(id); return next; });
      setRemoves((prev) => { const next = new Set(prev); next.delete(id); return next; });
    }
  };

  const save = async () => {
    setSaving(true);
    setError(null);
    try {
      await window.api('PATCH', `/api/projects/${projectId}/members/${member.userId}`, {
        roleId: overrideRoleId || null,
        customPermissions: { adds: [...adds], removes: [...removes] },
        excluded,
      });
      onSaved();
    } catch (e) {
      setError(e?.message || 'No se pudo guardar');
      setSaving(false);
    }
  };

  const resetToWorkspace = async () => {
    if (!confirm('¿Restablecer este miembro al rol del workspace? Se borra cualquier override que hayas configurado en este proyecto.')) return;
    setSaving(true);
    setError(null);
    try {
      await window.api('DELETE', `/api/projects/${projectId}/members/${member.userId}`);
      onSaved();
    } catch (e) {
      setError(e?.message || 'No se pudo resetear');
      setSaving(false);
    }
  };

  return (
    <Modal
      title={`${member.displayName} — config en este proyecto`}
      onClose={onClose}
      width={680}
      footer={<>
        <Button variant="ghost" onClick={resetToWorkspace} disabled={saving} icon="rotate-ccw">
          Heredar del workspace
        </Button>
        <div style={{ flex: 1 }} />
        <Button variant="ghost" onClick={onClose} disabled={saving}>Cancelar</Button>
        <Button variant="primary" onClick={save} disabled={saving}>
          {saving ? 'Guardando…' : 'Guardar'}
        </Button>
      </>}
    >
      {error && (
        <div style={{
          marginBottom: 12, padding: '8px 12px',
          background: 'rgba(239,68,68,.08)', border: '1px solid rgba(239,68,68,.30)',
          borderRadius: 6, color: 'var(--danger)', fontSize: 12,
        }}>{error}</div>
      )}

      <label style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 14, padding: 12, background: 'var(--bg-2)', borderRadius: 6, cursor: 'pointer' }}>
        <input
          type="checkbox"
          checked={excluded}
          onChange={(e) => setExcluded(e.target.checked)}
        />
        <div style={{ flex: 1 }}>
          <div style={{ fontSize: 13, fontWeight: 600, color: 'var(--text-0)' }}>Excluir de este proyecto</div>
          <div style={{ fontSize: 12, color: 'var(--text-2)' }}>El miembro pierde acceso total a este proyecto, sin importar el rol del workspace.</div>
        </div>
      </label>

      <label style={{ display: 'block', marginBottom: 14, opacity: excluded ? 0.5 : 1 }}>
        <div style={{ fontSize: 11, fontWeight: 600, color: 'var(--text-2)', textTransform: 'uppercase', marginBottom: 6 }}>
          Rol específico para este proyecto
        </div>
        <select
          value={overrideRoleId}
          onChange={(e) => setOverrideRoleId(e.target.value)}
          disabled={excluded}
          style={{
            width: '100%', padding: '10px 12px', background: 'var(--bg-1)',
            border: '1px solid var(--border)', borderRadius: 6, color: 'var(--text-0)',
            fontSize: 14,
          }}
        >
          <option value="">— Heredar del workspace —</option>
          {roles.map((r) => (
            <option key={r.id} value={r.id}>
              {r.name} ({r.permissions.length} permisos) {r.isSystem ? '' : '· custom'}
            </option>
          ))}
        </select>
      </label>

      <div style={{ background: 'var(--bg-2)', borderRadius: 6, padding: 12, fontSize: 12, marginBottom: 14 }}>
        <div style={{ color: 'var(--text-2)' }}>
          <strong style={{ color: excluded ? 'var(--danger)' : 'var(--text-1)' }}>{effective.size}</strong> permisos efectivos en este proyecto
          {(adds.size > 0 || removes.size > 0) && !excluded && (
            <span style={{ marginLeft: 8 }}>
              (rol: {baseline.size}
              {adds.size > 0 && <span style={{ color: '#10b981' }}> +{adds.size}</span>}
              {removes.size > 0 && <span style={{ color: 'var(--danger)' }}> −{removes.size}</span>}
              )
            </span>
          )}
        </div>
      </div>

      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 8, opacity: excluded ? 0.5 : 1 }}>
        <button className="btn-reset" onClick={() => setShowOverrides((v) => !v)} disabled={excluded} style={{
          fontSize: 11, fontWeight: 600, color: 'var(--text-2)', textTransform: 'uppercase',
          letterSpacing: 0.4, display: 'flex', alignItems: 'center', gap: 6, cursor: excluded ? 'not-allowed' : 'pointer',
        }}>
          <Icon name={showOverrides ? 'chevron-down' : 'chevron-right'} size={12} />
          Permisos custom para este proyecto <span style={{ color: 'var(--text-3)', fontWeight: 400 }}>· {adds.size + removes.size}</span>
        </button>
        {(adds.size > 0 || removes.size > 0) && !excluded && (
          <Button variant="ghost" size="sm" onClick={() => { setAdds(new Set()); setRemoves(new Set()); }}>Limpiar</Button>
        )}
      </div>

      {showOverrides && !excluded && (
        <div style={{
          border: '1px solid var(--border)', borderRadius: 8,
          maxHeight: 280, overflowY: 'auto',
        }}>
          {Object.entries(byCategory).map(([cat, list], idx) => (
            <div key={cat} style={{ borderTop: idx === 0 ? 'none' : '1px solid var(--border)' }}>
              <div style={{ padding: '8px 12px', background: 'var(--bg-2)', fontSize: 12, fontWeight: 600, color: 'var(--text-1)' }}>
                {_CATEGORY_LABEL[cat] || cat}
              </div>
              {list.map((p) => {
                const inBase = baseline.has(p.id);
                const isAdded = adds.has(p.id);
                const isRemoved = removes.has(p.id);
                const has = effective.has(p.id);
                let badge = null;
                if (isAdded) badge = <span style={{ marginLeft: 6, fontSize: 10, padding: '1px 5px', borderRadius: 3, background: 'rgba(16,185,129,.18)', color: '#10b981', fontWeight: 700 }}>+ ADD</span>;
                else if (isRemoved) badge = <span style={{ marginLeft: 6, fontSize: 10, padding: '1px 5px', borderRadius: 3, background: 'rgba(239,68,68,.18)', color: 'var(--danger)', fontWeight: 700 }}>− REMOVE</span>;
                else if (inBase) badge = <span style={{ marginLeft: 6, fontSize: 10, color: 'var(--text-3)' }}>del rol</span>;
                return (
                  <label key={p.id} style={{
                    display: 'flex', alignItems: 'flex-start', gap: 10,
                    padding: '8px 14px', cursor: 'pointer',
                    borderTop: '1px solid var(--border)',
                    opacity: has ? 1 : 0.55,
                  }}>
                    <input
                      type="checkbox"
                      checked={has}
                      onChange={() => togglePerm(p.id)}
                      style={{ marginTop: 2 }}
                    />
                    <div style={{ flex: 1 }}>
                      <span className="mono" style={{ fontSize: 11, color: 'var(--accent)' }}>{p.id}</span>
                      {badge}
                      <div style={{ fontSize: 12, color: 'var(--text-1)' }}>{p.description}</div>
                    </div>
                  </label>
                );
              })}
            </div>
          ))}
        </div>
      )}
    </Modal>
  );
}

window.ColaboradoresView = ColaboradoresView;
