'use client';
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import * as Dialog from '@radix-ui/react-dialog';
import { adminPortalContainer } from '../lib/portal-container.js';
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
import { Users as UsersIcon, Mail, ShieldCheck, Ticket, Search, Plus, X, Sparkles, ShieldAlert, MoreHorizontal, ChevronDown, Send, RefreshCw, Ban, AlertTriangle, Check, SlidersHorizontal, } from 'lucide-react';
import { useState, useMemo, useCallback } from 'react';
import { toast } from 'sonner';
import { useApiData } from '../lib/useApiData.js';
import { cmsApi } from '../lib/api.js';
import { Button } from '../components/ui/Button.js';
import { EmptyState } from '../components/ui/EmptyState.js';
import { Skeleton } from '../components/ui/Skeleton.js';
import { ConfirmDialog } from '../components/ui/ConfirmDialog.js';
import { UserDetailDrawer } from './users/UserDetailDrawer.js';
// ---------------------------------------------------------------------------
// Presentation constants (token-driven; brand = violet, matches screenshots)
// ---------------------------------------------------------------------------
const ROLE_PILL = {
    Owner: 'bg-brand/10 text-brand',
    Admin: 'bg-info/10 text-info',
    Editor: 'bg-accent text-accent-foreground',
    Viewer: 'bg-muted text-muted-foreground',
};
const STATUS_META = {
    active: { dot: 'bg-success', text: 'text-success', label: 'Active' },
    invited: { dot: 'bg-warning', text: 'text-warning', label: 'Invited' },
    expired: { dot: 'bg-destructive', text: 'text-destructive', label: 'Expired invite' },
    suspended: { dot: 'bg-muted-foreground', text: 'text-muted-foreground', label: 'Suspended' },
    locked: { dot: 'bg-destructive', text: 'text-destructive', label: 'Locked' },
    deactivated: { dot: 'bg-muted-foreground', text: 'text-muted-foreground', label: 'Deactivated' },
};
const SEVERITY_DOT = {
    critical: 'bg-destructive',
    high: 'bg-destructive',
    medium: 'bg-warning',
    low: 'bg-info',
};
const AVATAR_TINTS = ['bg-chart-1', 'bg-chart-2', 'bg-chart-3', 'bg-chart-4', 'bg-chart-5'];
const BASE_ROLE_OPTIONS = [
    {
        value: 'ADMIN',
        label: 'Admin',
        desc: 'Full access except billing and deleting the workspace.',
    },
    {
        value: 'EDITOR',
        label: 'Editor',
        desc: 'Create, edit and publish content. No settings access.',
    },
    { value: 'VIEWER', label: 'Viewer', desc: 'Read-only access to content and analytics.' },
];
const OWNER_ROLE_OPTION = {
    value: 'OWNER',
    label: 'Owner',
    desc: 'Full workspace control, including billing and Owner management.',
};
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
function avatarTint(id) {
    let h = 0;
    for (let i = 0; i < id.length; i++)
        h = (h * 31 + id.charCodeAt(i)) >>> 0;
    return AVATAR_TINTS[h % AVATAR_TINTS.length];
}
function relativeTime(iso) {
    if (!iso)
        return 'Never';
    const then = new Date(iso).getTime();
    if (Number.isNaN(then))
        return '—';
    const diffMs = Date.now() - then;
    const mins = Math.floor(diffMs / 60000);
    if (mins < 5)
        return 'Active now';
    if (mins < 60)
        return `${mins}m ago`;
    const hours = Math.floor(mins / 60);
    if (hours < 24)
        return `${hours}h ago`;
    const days = Math.floor(hours / 24);
    if (days === 1)
        return 'Yesterday';
    return `${days} days ago`;
}
// ---------------------------------------------------------------------------
// Metric cards
// ---------------------------------------------------------------------------
function MetricCard({ icon, label, value, sub, }) {
    return (_jsxs("div", { className: "border-border bg-card rounded-lg border p-4", children: [_jsxs("div", { className: "text-muted-foreground flex items-center gap-2", children: [icon, _jsx("span", { className: "text-sm", children: label })] }), _jsx("div", { className: "text-card-foreground mt-2 text-2xl font-medium", children: value }), sub && _jsx("div", { className: "text-muted-foreground mt-0.5 text-xs", children: sub })] }));
}
function MetricCards({ stats, loading }) {
    if (loading || !stats) {
        return (_jsx("div", { className: "grid grid-cols-2 gap-3 lg:grid-cols-4", children: Array.from({ length: 4 }, (_, i) => (_jsx(Skeleton, { variant: "card" }, i))) }));
    }
    const seatsValue = stats.seatLimit == null ? 'Unlimited' : `${stats.seatsUsed} / ${stats.seatLimit}`;
    return (_jsxs("div", { className: "grid grid-cols-2 gap-3 lg:grid-cols-4", children: [_jsx(MetricCard, { icon: _jsx(UsersIcon, { size: 16, "aria-hidden": true }), label: "Members", value: stats.members }), _jsx(MetricCard, { icon: _jsx(Mail, { size: 16, "aria-hidden": true }), label: "Pending invites", value: stats.pendingInvites }), _jsx(MetricCard, { icon: _jsx(ShieldCheck, { size: 16, "aria-hidden": true }), label: "Admins", value: stats.admins }), _jsx(MetricCard, { icon: _jsx(Ticket, { size: 16, "aria-hidden": true }), label: "Seats used", value: seatsValue, sub: stats.seatLimit == null ? 'No seat limit set' : undefined })] }));
}
// ---------------------------------------------------------------------------
// Access review banner
// ---------------------------------------------------------------------------
function AccessReviewBanner({ recommendations, onAct, onDismiss, }) {
    if (recommendations.length === 0)
        return null;
    const top = recommendations[0];
    const more = recommendations.length - 1;
    return (_jsxs("div", { className: "border-brand/20 bg-brand/5 flex items-start gap-3 rounded-lg border p-4", role: "status", "aria-live": "polite", children: [_jsx("span", { className: "bg-brand text-brand-foreground flex h-8 w-8 shrink-0 items-center justify-center rounded-full", children: _jsx(ShieldAlert, { size: 16, "aria-hidden": true }) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-card-foreground text-sm font-medium", children: top.title }), _jsxs("span", { className: "bg-brand/10 text-brand inline-flex items-center gap-1 rounded-full px-1.5 py-0.5 text-[10px] font-medium", children: [_jsx(Sparkles, { size: 10, "aria-hidden": true }), "AI"] }), _jsxs("span", { className: "text-muted-foreground inline-flex items-center gap-1 text-[10px]", children: [_jsx("span", { className: `h-1.5 w-1.5 rounded-full ${SEVERITY_DOT[top.severity]}` }), top.severity] })] }), _jsx("p", { className: "text-muted-foreground mt-0.5 text-sm", children: top.description }), more > 0 && (_jsxs("p", { className: "text-muted-foreground mt-1 text-xs", children: ["+", more, " more recommendation", more > 1 ? 's' : ''] }))] }), _jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [_jsx(Button, { size: "sm", variant: "primary", onClick: () => onAct(top), children: top.recommendedAction }), _jsx("button", { type: "button", onClick: () => onDismiss(top), className: "text-muted-foreground hover:bg-accent rounded-md p-1.5 transition-colors", "aria-label": "Dismiss recommendation", children: _jsx(X, { size: 16, "aria-hidden": true }) })] })] }));
}
// ---------------------------------------------------------------------------
// Avatar + row pieces
// ---------------------------------------------------------------------------
function Avatar({ row }) {
    return (_jsx("span", { className: `flex h-9 w-9 shrink-0 items-center justify-center rounded-full text-xs font-medium text-white ${avatarTint(row.id)}`, "aria-hidden": true, children: row.initials }));
}
function RoleDropdown({ row, options, disabled, onChange, }) {
    const pill = ROLE_PILL[row.roleName] ?? ROLE_PILL.Viewer;
    if (disabled) {
        return (_jsx("span", { className: `inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${pill}`, children: row.roleName }));
    }
    return (_jsxs(DropdownMenu.Root, { children: [_jsx(DropdownMenu.Trigger, { asChild: true, children: _jsxs("button", { type: "button", className: `inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium transition-opacity hover:opacity-80 ${pill}`, "aria-label": `Change role for ${row.name}, current role ${row.roleName}`, children: [row.roleName, _jsx(ChevronDown, { size: 12, "aria-hidden": true })] }) }), _jsx(DropdownMenu.Portal, { container: adminPortalContainer(), children: _jsx(DropdownMenu.Content, { align: "start", sideOffset: 4, className: "border-border bg-card z-50 min-w-44 rounded-lg border p-1 shadow-md", children: options.map((opt) => (_jsxs(DropdownMenu.Item, { onSelect: () => onChange(opt.value), className: "text-card-foreground data-highlighted:bg-accent flex cursor-pointer items-center justify-between gap-2 rounded-md px-2 py-1.5 text-sm outline-none", children: [opt.label, row.roleName.toUpperCase() === opt.value && (_jsx(Check, { size: 14, className: "text-brand", "aria-hidden": true }))] }, opt.value))) }) })] }));
}
function StatusBadge({ status }) {
    const meta = STATUS_META[status];
    return (_jsxs("span", { className: `inline-flex items-center gap-1.5 text-xs ${meta.text}`, children: [_jsx("span", { className: `h-1.5 w-1.5 rounded-full ${meta.dot}`, "aria-hidden": true }), meta.label] }));
}
// ---------------------------------------------------------------------------
// Invite modal
// ---------------------------------------------------------------------------
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
function InviteModal({ open, onOpenChange, roleOptions, emailConfigured, seatsFull, onInvited, }) {
    const [email, setEmail] = useState('');
    const [role, setRole] = useState('EDITOR');
    const [message, setMessage] = useState('');
    const [expiresInDays, setExpiresInDays] = useState(7);
    const [showAdvanced, setShowAdvanced] = useState(false);
    const [sending, setSending] = useState(false);
    const [emailError, setEmailError] = useState(null);
    const reset = useCallback(() => {
        setEmail('');
        setRole('EDITOR');
        setMessage('');
        setExpiresInDays(7);
        setShowAdvanced(false);
        setEmailError(null);
    }, []);
    const handleSubmit = async (e) => {
        e.preventDefault();
        if (sending)
            return;
        const trimmed = email.trim();
        if (!EMAIL_RE.test(trimmed)) {
            setEmailError('Enter a valid email address.');
            return;
        }
        setSending(true);
        try {
            const res = await cmsApi('/users/invite', {
                method: 'POST',
                body: JSON.stringify({
                    email: trimmed,
                    role,
                    message: message.trim() || undefined,
                    expiresInDays,
                }),
            });
            if (res.error) {
                toast.error(res.error);
                return;
            }
            if (res.data?.emailed) {
                toast.success(`Invitation sent to ${trimmed}`);
            }
            else if (res.data?.inviteUrl) {
                await navigator.clipboard?.writeText(res.data.inviteUrl).catch(() => { });
                toast.success('Invite created. Link copied — email isn’t configured, so share it manually.', {
                    duration: 8000,
                });
            }
            else {
                toast.success('Invitation created.');
            }
            onOpenChange(false);
            reset();
            onInvited();
        }
        catch {
            toast.error('Could not create the invitation. Please try again.');
        }
        finally {
            setSending(false);
        }
    };
    return (_jsx(Dialog.Root, { open: open, onOpenChange: (v) => {
            onOpenChange(v);
            if (!v)
                reset();
        }, children: _jsxs(Dialog.Portal, { container: adminPortalContainer(), children: [_jsx(Dialog.Overlay, { className: "fixed inset-0 z-50 bg-black/40 backdrop-blur-sm" }), _jsxs(Dialog.Content, { className: "border-border bg-card fixed top-1/2 left-1/2 z-50 max-h-[90vh] w-[calc(100vw-2rem)] max-w-md -translate-x-1/2 -translate-y-1/2 overflow-y-auto rounded-lg border p-6 shadow-lg focus:outline-none", children: [_jsxs("div", { className: "mb-4 flex items-center justify-between", children: [_jsx(Dialog.Title, { className: "text-card-foreground text-lg font-medium", children: "Invite a user" }), _jsx(Dialog.Close, { asChild: true, children: _jsx("button", { type: "button", className: "text-muted-foreground hover:bg-accent rounded-md p-1 transition-colors", "aria-label": "Close", children: _jsx(X, { size: 18, "aria-hidden": true }) }) })] }), seatsFull && (_jsxs("div", { className: "border-warning/30 bg-warning/10 text-warning mb-4 flex items-start gap-2 rounded-md border p-3 text-sm", children: [_jsx(AlertTriangle, { size: 16, className: "mt-0.5 shrink-0", "aria-hidden": true }), _jsx("span", { children: "All seats are in use. Remove a member or raise the seat limit before inviting." })] })), !emailConfigured && !seatsFull && (_jsxs("div", { className: "border-border bg-muted/50 text-muted-foreground mb-4 flex items-start gap-2 rounded-md border p-3 text-sm", children: [_jsx(Mail, { size: 16, className: "mt-0.5 shrink-0", "aria-hidden": true }), _jsx("span", { children: "Email isn\u2019t configured \u2014 you\u2019ll get a shareable invite link to send manually." })] })), _jsxs("form", { onSubmit: handleSubmit, className: "space-y-4", children: [_jsxs("div", { children: [_jsx("label", { htmlFor: "invite-email", className: "text-card-foreground mb-1 block text-sm font-medium", children: "Email address" }), _jsx("input", { id: "invite-email", type: "email", value: email, onChange: (e) => {
                                                setEmail(e.target.value);
                                                if (emailError)
                                                    setEmailError(null);
                                            }, placeholder: "name@company.com", autoComplete: "off", "aria-invalid": !!emailError, "aria-describedby": emailError ? 'invite-email-error' : undefined, className: "border-border bg-input-background text-foreground focus:ring-brand w-full rounded-md border px-3 py-2 text-sm focus:ring-2 focus:outline-none", required: true }), emailError && (_jsx("p", { id: "invite-email-error", className: "text-destructive mt-1 text-xs", role: "alert", children: emailError }))] }), _jsxs("fieldset", { children: [_jsx("legend", { className: "text-card-foreground mb-2 block text-sm font-medium", children: "Role" }), _jsx("div", { className: "space-y-2", children: roleOptions.map((opt) => {
                                                const active = role === opt.value;
                                                return (_jsxs("label", { className: `flex cursor-pointer items-start gap-3 rounded-md border p-3 transition-colors ${active ? 'border-brand bg-brand/5' : 'border-border hover:bg-accent/50'}`, children: [_jsx("input", { type: "radio", name: "invite-role", value: opt.value, checked: active, onChange: () => setRole(opt.value), className: "accent-brand mt-0.5" }), _jsxs("span", { className: "min-w-0", children: [_jsx("span", { className: "text-card-foreground block text-sm font-medium", children: opt.label }), _jsx("span", { className: "text-muted-foreground block text-xs", children: opt.desc })] })] }, opt.value));
                                            }) })] }), _jsxs("div", { children: [_jsxs("button", { type: "button", onClick: () => setShowAdvanced((v) => !v), className: "text-muted-foreground hover:text-foreground flex items-center gap-1 text-xs font-medium transition-colors", "aria-expanded": showAdvanced, children: [_jsx(ChevronDown, { size: 14, className: `transition-transform ${showAdvanced ? 'rotate-180' : ''}`, "aria-hidden": true }), "Advanced options"] }), showAdvanced && (_jsxs("div", { className: "mt-3 space-y-3", children: [_jsxs("div", { children: [_jsxs("label", { htmlFor: "invite-message", className: "text-card-foreground mb-1 block text-sm font-medium", children: ["Custom message", ' ', _jsx("span", { className: "text-muted-foreground font-normal", children: "(optional)" })] }), _jsx("textarea", { id: "invite-message", value: message, onChange: (e) => setMessage(e.target.value), rows: 2, placeholder: "Add a personal note to the invitation email.", className: "border-border bg-input-background text-foreground focus:ring-brand w-full rounded-md border px-3 py-2 text-sm focus:ring-2 focus:outline-none" })] }), _jsxs("div", { children: [_jsx("label", { htmlFor: "invite-expiry", className: "text-card-foreground mb-1 block text-sm font-medium", children: "Invite expires in" }), _jsxs("select", { id: "invite-expiry", value: expiresInDays, onChange: (e) => setExpiresInDays(Number(e.target.value)), className: "border-border bg-input-background text-foreground focus:ring-brand w-full rounded-md border px-3 py-2 text-sm focus:ring-2 focus:outline-none", children: [_jsx("option", { value: 7, children: "7 days" }), _jsx("option", { value: 14, children: "14 days" }), _jsx("option", { value: 30, children: "30 days" })] })] })] }))] }), _jsxs("div", { className: "flex items-center justify-end gap-3 pt-2", children: [_jsx(Dialog.Close, { asChild: true, children: _jsx(Button, { type: "button", variant: "outline", children: "Cancel" }) }), _jsx(Button, { type: "submit", variant: "primary", loading: sending, disabled: sending || seatsFull || !email.trim(), leftIcon: !sending ? _jsx(Send, { size: 16, "aria-hidden": true }) : undefined, children: sending ? 'Sending…' : 'Send invite' })] })] })] })] }) }));
}
// ---------------------------------------------------------------------------
// Main view
// ---------------------------------------------------------------------------
const ROLE_PILLS = [
    'All',
    'Owner',
    'Admin',
    'Editor',
    'Viewer',
];
export function Users({ embedded = false } = {}) {
    const members = useApiData('/users');
    const invites = useApiData('/invites');
    const stats = useApiData('/users/stats');
    const review = useApiData('/access-review');
    const [search, setSearch] = useState('');
    const [roleFilter, setRoleFilter] = useState('All');
    const [showInvite, setShowInvite] = useState(false);
    const [detailUserId, setDetailUserId] = useState(null);
    const [confirm, setConfirm] = useState(null);
    const refetchAll = useCallback(() => {
        members.refetch();
        invites.refetch();
        stats.refetch();
        review.refetch();
    }, [members, invites, stats, review]);
    const currentUserRole = useMemo(() => (members.data ?? []).find((m) => m.isCurrentUser)?.roleName ?? null, [members.data]);
    const isOwner = currentUserRole === 'Owner';
    const roleOptions = useMemo(() => (isOwner ? [OWNER_ROLE_OPTION, ...BASE_ROLE_OPTIONS] : BASE_ROLE_OPTIONS), [isOwner]);
    const rows = useMemo(() => {
        const memberRows = (members.data ?? []).map((m) => ({
            kind: 'member',
            id: m.id,
            name: m.name,
            email: m.email,
            initials: m.initials,
            role: m.role,
            roleName: m.roleName,
            status: m.status,
            mfaEnabled: m.mfaEnabled,
            externalDomain: m.externalDomain,
            activityAt: m.lastActiveAt ?? m.lastLoginAt,
            isCurrentUser: m.isCurrentUser,
        }));
        const inviteRows = (invites.data ?? [])
            .filter((i) => i.status === 'PENDING' || i.status === 'EXPIRED')
            .map((i) => ({
            kind: 'invite',
            id: i.id,
            name: i.email.split('@')[0] ?? i.email,
            email: i.email,
            initials: (i.email.slice(0, 2) || '?').toUpperCase(),
            role: i.role,
            roleName: i.roleName,
            status: i.expired || i.status === 'EXPIRED' ? 'expired' : 'invited',
            mfaEnabled: false,
            externalDomain: false,
            activityAt: i.createdAt,
            isCurrentUser: false,
        }));
        return [...memberRows, ...inviteRows];
    }, [members.data, invites.data]);
    const filteredRows = useMemo(() => {
        let r = rows;
        if (roleFilter !== 'All')
            r = r.filter((row) => row.roleName === roleFilter);
        if (search.trim()) {
            const q = search.trim().toLowerCase();
            r = r.filter((row) => row.name.toLowerCase().includes(q) ||
                row.email.toLowerCase().includes(q) ||
                row.roleName.toLowerCase().includes(q) ||
                row.status.includes(q));
        }
        return r;
    }, [rows, roleFilter, search]);
    // -------------------------------------------------------------------------
    // Actions
    // -------------------------------------------------------------------------
    const changeMemberRole = useCallback(async (row, role) => {
        if (role.toUpperCase() === row.role.toUpperCase())
            return;
        const endpoint = row.kind === 'invite' ? `/invites/${row.id}` : `/users/${row.id}/role`;
        const res = await cmsApi(endpoint, { method: 'PATCH', body: JSON.stringify({ role }) });
        if (res.error) {
            toast.error(res.error);
            return;
        }
        toast.success(`Role updated to ${role.charAt(0) + role.slice(1).toLowerCase()}`);
        refetchAll();
    }, [refetchAll]);
    const requestRoleChange = useCallback((row, role) => {
        if (role === 'OWNER' || row.roleName === 'Owner') {
            setConfirm({
                title: role === 'OWNER' ? 'Assign Owner role' : 'Change Owner’s role',
                description: role === 'OWNER'
                    ? `Owners have full control of the workspace, including billing and managing other Owners. Make ${row.name} an Owner?`
                    : `This will change ${row.name}'s role away from Owner. Continue?`,
                confirmLabel: 'Change role',
                onConfirm: () => changeMemberRole(row, role),
            });
            return;
        }
        changeMemberRole(row, role);
    }, [changeMemberRole]);
    const revokeMember = useCallback((row) => {
        setConfirm({
            title: 'Revoke access',
            description: `${row.name} will lose access immediately and all their sessions will be revoked. Their content and history are preserved. This can be reversed later.`,
            confirmLabel: 'Revoke access',
            destructive: true,
            onConfirm: async () => {
                const res = await cmsApi(`/users/${row.id}`, { method: 'DELETE' });
                if (res.error) {
                    toast.error(res.error);
                    return;
                }
                toast.success(`Access revoked for ${row.name}`);
                refetchAll();
            },
        });
    }, [refetchAll]);
    const cancelInvite = useCallback((row) => {
        setConfirm({
            title: 'Cancel invitation',
            description: `The invitation to ${row.email} will be cancelled and the link will stop working.`,
            confirmLabel: 'Cancel invite',
            destructive: true,
            onConfirm: async () => {
                const res = await cmsApi(`/invites/${row.id}/cancel`, { method: 'POST' });
                if (res.error) {
                    toast.error(res.error);
                    return;
                }
                toast.success('Invitation cancelled');
                refetchAll();
            },
        });
    }, [refetchAll]);
    const resendInvite = useCallback(async (row) => {
        const res = await cmsApi(`/invites/${row.id}/resend`, { method: 'POST' });
        if (res.error) {
            toast.error(res.error);
            return;
        }
        if (res.data?.emailed) {
            toast.success(`Invitation re-sent to ${row.email}`);
        }
        else if (res.data?.inviteUrl) {
            await navigator.clipboard?.writeText(res.data.inviteUrl).catch(() => { });
            toast.success('New invite link copied — email isn’t configured, so share it manually.', {
                duration: 8000,
            });
        }
        else {
            toast.success('Invitation re-sent');
        }
        refetchAll();
    }, [refetchAll]);
    const actOnRecommendation = useCallback((rec) => {
        const target = rows.find((r) => r.id === rec.userId || r.id === rec.inviteId);
        if (rec.type === 'stale_invite' && target) {
            cancelInvite(target);
            return;
        }
        if (target) {
            revokeMember(target);
            return;
        }
        toast.info('Open the member to action this recommendation.');
    }, [rows, cancelInvite, revokeMember]);
    const dismissRecommendation = useCallback(async (rec) => {
        const res = await cmsApi(`/access-review/${encodeURIComponent(rec.id)}/dismiss`, {
            method: 'POST',
        });
        if (res.error) {
            toast.error(res.error);
            return;
        }
        toast.success('Recommendation dismissed');
        review.refetch();
    }, [review]);
    // -------------------------------------------------------------------------
    // Render
    // -------------------------------------------------------------------------
    const rootPadding = embedded ? '' : 'p-3 pr-6 sm:p-4 sm:pr-8';
    const loading = members.loading || invites.loading;
    const loadError = members.error || invites.error;
    const seatsFull = stats.data?.seatsAvailable != null && stats.data.seatsAvailable <= 0;
    const peopleCount = (members.data?.length ?? 0) + rows.filter((r) => r.kind === 'invite').length;
    return (_jsxs("div", { className: `space-y-4 ${rootPadding}`, children: [_jsx(MetricCards, { stats: stats.data, loading: stats.loading }), !review.loading && review.data && review.data.length > 0 && (_jsx(AccessReviewBanner, { recommendations: review.data, onAct: actOnRecommendation, onDismiss: dismissRecommendation })), _jsxs("div", { className: "border-border bg-card rounded-lg border", children: [_jsxs("div", { className: "border-border flex flex-col gap-3 border-b p-4 lg:flex-row lg:items-center lg:justify-between", children: [_jsxs("div", { children: [_jsx("h2", { className: "text-card-foreground text-lg font-medium", children: "Team members" }), _jsxs("p", { className: "text-muted-foreground text-sm", children: [peopleCount, " ", peopleCount === 1 ? 'person has' : 'people have', " access to this workspace"] })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs("div", { className: "relative", children: [_jsx(Search, { size: 16, className: "text-muted-foreground absolute top-1/2 left-3 -translate-y-1/2", "aria-hidden": true }), _jsx("input", { type: "text", value: search, onChange: (e) => setSearch(e.target.value), placeholder: "Search by name or email\u2026", "aria-label": "Search team members", className: "border-border bg-input-background text-foreground focus:ring-brand w-full rounded-md border py-2 pr-3 pl-9 text-sm focus:ring-2 focus:outline-none lg:w-64" })] }), _jsx(Button, { variant: "primary", onClick: () => setShowInvite(true), disabled: seatsFull, title: seatsFull ? 'All seats are in use' : undefined, leftIcon: _jsx(Plus, { size: 16, "aria-hidden": true }), children: "Invite user" })] })] }), _jsx("div", { className: "border-border flex flex-wrap gap-2 border-b px-4 py-3", role: "group", "aria-label": "Filter by role", children: ROLE_PILLS.map((pill) => {
                            const active = roleFilter === pill;
                            return (_jsx("button", { type: "button", onClick: () => setRoleFilter(pill), "aria-pressed": active, className: `rounded-full px-3 py-1 text-xs font-medium transition-colors ${active
                                    ? 'bg-brand text-brand-foreground'
                                    : 'bg-muted text-muted-foreground hover:bg-accent'}`, children: pill }, pill));
                        }) }), loading ? (_jsx(Skeleton, { variant: "table-row", lines: 5, className: "p-2" })) : loadError ? (_jsxs("div", { className: "flex items-center gap-3 p-4", children: [_jsx(AlertTriangle, { size: 18, className: "text-destructive shrink-0", "aria-hidden": true }), _jsx("span", { className: "text-destructive flex-1 text-sm", children: loadError }), _jsx(Button, { variant: "outline", size: "sm", onClick: refetchAll, children: "Retry" })] })) : rows.length === 0 ? (_jsx(EmptyState, { icon: _jsx(UsersIcon, { size: 24, "aria-hidden": true }), title: "No team members yet", description: "Invite your first teammate to collaborate in this workspace.", actionLabel: "Invite user", onAction: () => setShowInvite(true) })) : filteredRows.length === 0 ? (_jsx(EmptyState, { icon: _jsx(Search, { size: 24, "aria-hidden": true }), title: "No users match your filters", description: "Try a different search or role filter.", actionLabel: "Clear filters", onAction: () => {
                            setSearch('');
                            setRoleFilter('All');
                        } })) : (_jsx("div", { className: "overflow-x-auto", children: _jsxs("table", { className: "w-full min-w-[640px]", children: [_jsx("thead", { children: _jsxs("tr", { className: "border-border text-muted-foreground border-b text-left text-xs", children: [_jsx("th", { className: "px-4 py-2 font-medium", children: "Member" }), _jsx("th", { className: "px-4 py-2 font-medium", children: "Role" }), _jsx("th", { className: "px-4 py-2 font-medium", children: "Status" }), _jsx("th", { className: "px-4 py-2 font-medium", children: "Last active" }), _jsx("th", { className: "px-4 py-2 font-medium", children: _jsx("span", { className: "sr-only", children: "Actions" }) })] }) }), _jsx("tbody", { className: "divide-border divide-y", children: filteredRows.map((row) => {
                                        const isInvite = row.kind === 'invite';
                                        // Last Owner / self protections are enforced server-side; here we
                                        // only disable the obviously-invalid affordances.
                                        const roleChangeDisabled = (row.roleName === 'Owner' && !isOwner) ||
                                            (row.roleName === 'Owner' && row.isCurrentUser);
                                        const openDetail = () => {
                                            if (!isInvite)
                                                setDetailUserId(row.id);
                                        };
                                        return (_jsxs("tr", { className: `hover:bg-accent/30 transition-colors ${isInvite ? '' : 'cursor-pointer'}`, ...(isInvite
                                                ? {}
                                                : {
                                                    role: 'button',
                                                    tabIndex: 0,
                                                    'aria-label': `Open details for ${row.name}`,
                                                    onClick: openDetail,
                                                    onKeyDown: (e) => {
                                                        if (e.key === 'Enter' || e.key === ' ') {
                                                            e.preventDefault();
                                                            openDetail();
                                                        }
                                                    },
                                                }), children: [_jsx("td", { className: "px-4 py-3", children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx(Avatar, { row: row }), _jsxs("div", { className: "min-w-0", children: [_jsxs("div", { className: "text-card-foreground flex items-center gap-1.5 text-sm font-medium", children: [_jsx("span", { className: "truncate", children: row.name }), row.isCurrentUser && (_jsx("span", { className: "bg-muted text-muted-foreground rounded px-1 text-[10px]", children: "You" })), row.externalDomain && (_jsx("span", { className: "bg-warning/10 text-warning rounded px-1 text-[10px]", children: "External" }))] }), _jsx("div", { className: "text-muted-foreground truncate text-xs", children: row.email })] })] }) }), _jsx("td", { className: "px-4 py-3", onClick: (e) => e.stopPropagation(), role: "presentation", children: _jsx(RoleDropdown, { row: row, options: roleOptions, disabled: roleChangeDisabled, onChange: (role) => requestRoleChange(row, role) }) }), _jsx("td", { className: "px-4 py-3", children: _jsx(StatusBadge, { status: row.status }) }), _jsx("td", { className: "text-muted-foreground px-4 py-3 text-sm", children: isInvite
                                                        ? `Invited ${relativeTime(row.activityAt)}`
                                                        : relativeTime(row.activityAt) }), _jsx("td", { className: "px-4 py-3 text-right", onClick: (e) => e.stopPropagation(), role: "presentation", children: _jsx(RowActions, { row: row, isInvite: isInvite, onResend: () => resendInvite(row), onCancelInvite: () => cancelInvite(row), onRevoke: () => revokeMember(row), onViewDetails: isInvite ? undefined : () => setDetailUserId(row.id), canRevoke: !row.isCurrentUser }) })] }, `${row.kind}-${row.id}`));
                                    }) })] }) }))] }), _jsx(InviteModal, { open: showInvite, onOpenChange: setShowInvite, roleOptions: roleOptions, emailConfigured: stats.data?.emailConfigured ?? true, seatsFull: !!seatsFull, onInvited: refetchAll }), confirm && (_jsx(ConfirmDialog, { open: !!confirm, onClose: () => setConfirm(null), onConfirm: confirm.onConfirm, title: confirm.title, description: confirm.description, confirmLabel: confirm.confirmLabel, destructive: confirm.destructive })), _jsx(UserDetailDrawer, { userId: detailUserId, open: detailUserId !== null, onOpenChange: (v) => {
                    if (!v)
                        setDetailUserId(null);
                }, onChanged: refetchAll })] }));
}
function RowActions({ row, isInvite, onResend, onCancelInvite, onRevoke, onViewDetails, canRevoke, }) {
    return (_jsxs(DropdownMenu.Root, { children: [_jsx(DropdownMenu.Trigger, { asChild: true, children: _jsx("button", { type: "button", className: "text-muted-foreground hover:bg-accent rounded-md p-1.5 transition-colors", "aria-label": `Actions for ${row.name}`, children: _jsx(MoreHorizontal, { size: 16, "aria-hidden": true }) }) }), _jsx(DropdownMenu.Portal, { container: adminPortalContainer(), children: _jsx(DropdownMenu.Content, { align: "end", sideOffset: 4, className: "border-border bg-card z-50 min-w-44 rounded-lg border p-1 shadow-md", children: isInvite ? (_jsxs(_Fragment, { children: [_jsxs(DropdownMenu.Item, { onSelect: onResend, className: "text-card-foreground data-highlighted:bg-accent flex cursor-pointer items-center gap-2 rounded-md px-2 py-1.5 text-sm outline-none", children: [_jsx(RefreshCw, { size: 14, "aria-hidden": true }), "Resend invite"] }), _jsxs(DropdownMenu.Item, { onSelect: onCancelInvite, className: "text-destructive data-highlighted:bg-destructive/10 flex cursor-pointer items-center gap-2 rounded-md px-2 py-1.5 text-sm outline-none", children: [_jsx(Ban, { size: 14, "aria-hidden": true }), "Cancel invite"] })] })) : (_jsxs(_Fragment, { children: [onViewDetails && (_jsxs(DropdownMenu.Item, { onSelect: onViewDetails, className: "text-card-foreground data-highlighted:bg-accent flex cursor-pointer items-center gap-2 rounded-md px-2 py-1.5 text-sm outline-none", children: [_jsx(SlidersHorizontal, { size: 14, "aria-hidden": true }), "View details"] })), _jsxs(DropdownMenu.Item, { onSelect: canRevoke ? onRevoke : undefined, disabled: !canRevoke, className: "text-destructive data-highlighted:bg-destructive/10 flex cursor-pointer items-center gap-2 rounded-md px-2 py-1.5 text-sm outline-none data-disabled:cursor-not-allowed data-disabled:opacity-50", children: [_jsx(Ban, { size: 14, "aria-hidden": true }), "Revoke access"] })] })) }) })] }));
}
//# sourceMappingURL=Users.js.map