"use client";
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useCallback, useState } from "react";
import { cn } from "@stigmer/theme";
import { getUserMessage, iamRoleDisplayName, } from "@stigmer/sdk";
import { IamRole } from "@stigmer/protos/ai/stigmer/iam/v1/enum_pb";
import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
import { InvitationState } from "@stigmer/protos/ai/stigmer/iam/invitation/v1/enum_pb";
import { timestampDate } from "@bufbuild/protobuf/wkt";
import { useOrgInvitations } from "./useOrgInvitations";
import { useCreateInvitation } from "./useCreateInvitation";
import { useRevokeInvitation } from "./useRevokeInvitation";
import { InvitationCreatedAlert } from "./InvitationCreatedAlert";
import { RoleSelector } from "../iam-policy/RoleSelector";
function defaultBuildInviteUrl(token) {
    if (typeof window !== "undefined") {
        return `${window.location.origin}/invite/${token}`;
    }
    return `/invite/${token}`;
}
/**
 * Self-contained panel for managing organization invitation links.
 *
 * Displays all invitations for the organization with inline actions
 * (copy link, revoke) and a create form for generating new invite
 * links. Follows the same pattern as {@link OrgMembersPanel}: a
 * single embeddable component that handles the full management flow.
 *
 * The component composes three invitation hooks internally:
 * - {@link useOrgInvitations} for listing
 * - {@link useCreateInvitation} for creation
 * - {@link useRevokeInvitation} for revocation
 *
 * All visual properties flow through `--stgm-*` design tokens.
 *
 * @example
 * ```tsx
 * <InvitationManager org="acme" />
 *
 * <InvitationManager
 *   org="acme"
 *   buildInviteUrl={(token) => `https://myapp.com/join/${token}`}
 * />
 * ```
 */
export function InvitationManager({ org, buildInviteUrl = defaultBuildInviteUrl, className, }) {
    const { invitations, isLoading, error, refetch } = useOrgInvitations(org);
    const [flow, setFlow] = useState({ phase: "idle" });
    const [revokingId, setRevokingId] = useState(null);
    const activeCount = invitations.filter((inv) => inv.status?.state === InvitationState.active).length;
    const handleCreated = useCallback((invitation) => {
        const token = invitation.status?.token ?? "";
        const label = invitation.spec?.label || invitation.metadata?.name || "Invite link";
        setFlow({ phase: "created", inviteUrl: buildInviteUrl(token), label });
        refetch();
    }, [buildInviteUrl, refetch]);
    const handleRevoked = useCallback(() => {
        setRevokingId(null);
        refetch();
    }, [refetch]);
    if (isLoading) {
        return (_jsx("div", { className: cn("space-y-2", className), "aria-busy": "true", "aria-label": "Loading invitations", children: Array.from({ length: 2 }, (_, i) => (_jsx("div", { className: "bg-muted-subtle h-14 animate-pulse rounded-lg" }, i))) }));
    }
    if (error) {
        return (_jsx("p", { className: cn("text-destructive text-xs", className), role: "alert", children: getUserMessage(error) }));
    }
    return (_jsxs("div", { className: cn("space-y-3", className), children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-sm font-semibold text-foreground", children: "Invite Links" }), activeCount > 0 && (_jsxs("span", { className: "inline-flex items-center rounded-full bg-muted px-2 py-0.5 text-[0.65rem] font-medium text-muted-foreground", children: [activeCount, " active"] }))] }), flow.phase === "idle" && (_jsx("button", { type: "button", onClick: () => setFlow({ phase: "creating" }), className: "text-primary hover:text-foreground text-xs font-medium transition-colors", children: "+ Create invite link" }))] }), flow.phase === "created" && (_jsx(InvitationCreatedAlert, { inviteUrl: flow.inviteUrl, label: flow.label, onDismiss: () => setFlow({ phase: "idle" }) })), flow.phase === "creating" && (_jsx("div", { className: "border-border bg-card rounded-lg border p-4", children: _jsx(CreateInvitationForm, { org: org, onCreated: handleCreated, onCancel: () => setFlow({ phase: "idle" }) }) })), invitations.length === 0 ? (_jsx("p", { className: "text-muted-foreground py-4 text-center text-xs", children: "No invite links yet. Create one to start inviting people." })) : (_jsx("div", { role: "list", "aria-label": "Invitation links", className: "space-y-2", children: invitations.map((inv) => {
                    const id = inv.metadata?.id ?? "";
                    return (_jsx(InvitationRow, { invitation: inv, buildInviteUrl: buildInviteUrl, isRevoking: revokingId === id, onStartRevoke: () => setRevokingId(id), onCancelRevoke: () => setRevokingId(null), onRevoked: handleRevoked }, id));
                }) }))] }));
}
const EXPIRY_OPTIONS = [
    { value: "7", label: "7 days" },
    { value: "14", label: "14 days" },
    { value: "30", label: "30 days" },
];
function CreateInvitationForm({ org, onCreated, onCancel, }) {
    const { create, isCreating, error, clearError } = useCreateInvitation();
    const [label, setLabel] = useState("");
    const [role, setRole] = useState(IamRole.viewer);
    const [expiry, setExpiry] = useState("30");
    const [redemptionMode, setRedemptionMode] = useState("unlimited");
    const canSubmit = !isCreating;
    const handleSubmit = useCallback(async (e) => {
        e.preventDefault();
        if (!canSubmit)
            return;
        clearError();
        try {
            const invitation = await create({
                name: label.trim() || `invite-${Date.now()}`,
                org,
                role,
                expiresAt: daysFromNow(Number(expiry)),
                maxRedemptions: redemptionMode === "single" ? 1 : 0,
                label: label.trim() || undefined,
            });
            onCreated(invitation);
        }
        catch {
            // error state is managed by useCreateInvitation
        }
    }, [canSubmit, label, org, role, expiry, redemptionMode, create, clearError, onCreated]);
    return (_jsxs("form", { onSubmit: handleSubmit, className: "space-y-3", children: [_jsxs("div", { className: "space-y-1", children: [_jsxs("label", { htmlFor: "stgm-new-invite-label", className: "text-xs font-medium text-foreground", children: ["Label", " ", _jsx("span", { className: "font-normal text-muted-foreground", children: "(optional)" })] }), _jsx("input", { id: "stgm-new-invite-label", type: "text", value: label, onChange: (e) => setLabel(e.target.value), placeholder: "e.g. Engineering team link", disabled: isCreating, autoFocus: true, maxLength: 200, className: cn("w-full rounded-md border border-input bg-background px-2.5 py-1.5 text-xs text-foreground", "placeholder:text-muted-foreground", "focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring", "disabled:pointer-events-none disabled:opacity-50") })] }), _jsx(RoleSelector, { kind: ApiResourceKind.organization, selected: role, onSelect: (r) => setRole(r), disabled: isCreating }), _jsxs("fieldset", { className: "space-y-1.5", children: [_jsx("legend", { className: "text-xs font-medium text-foreground", children: "Expires in" }), _jsx("div", { className: "flex flex-wrap gap-2", children: EXPIRY_OPTIONS.map(({ value, label: optLabel }) => (_jsx(ExpiryRadio, { value: value, label: optLabel, checked: expiry === value, disabled: isCreating, onChange: setExpiry }, value))) })] }), _jsxs("fieldset", { className: "space-y-1.5", children: [_jsx("legend", { className: "text-xs font-medium text-foreground", children: "Usage limit" }), _jsxs("div", { className: "flex flex-wrap gap-2", children: [_jsx(RedemptionRadio, { value: "unlimited", label: "Unlimited", description: "Anyone with the link can join", checked: redemptionMode === "unlimited", disabled: isCreating, onChange: setRedemptionMode }), _jsx(RedemptionRadio, { value: "single", label: "Single use", description: "One person only", checked: redemptionMode === "single", disabled: isCreating, onChange: setRedemptionMode })] })] }), error && (_jsx("p", { className: "text-destructive text-[0.65rem]", role: "alert", children: getUserMessage(error) })), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs("button", { type: "submit", disabled: !canSubmit, className: cn("inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium", "bg-primary text-primary-foreground hover:bg-primary-hover", "disabled:pointer-events-none disabled:opacity-40"), children: [isCreating && _jsx(SpinnerIcon, {}), "Create invite link"] }), _jsx("button", { type: "button", onClick: onCancel, disabled: isCreating, className: cn("rounded-md px-3 py-1.5 text-xs", "text-muted-foreground hover:text-foreground hover:bg-accent-hover", "disabled:pointer-events-none disabled:opacity-50"), children: "Cancel" })] })] }));
}
// ---------------------------------------------------------------------------
// InvitationRow (internal)
// ---------------------------------------------------------------------------
function InvitationRow({ invitation, buildInviteUrl, isRevoking, onStartRevoke, onCancelRevoke, onRevoked, }) {
    const [copied, setCopied] = useState(false);
    const id = invitation.metadata?.id ?? "";
    const label = invitation.spec?.label || invitation.metadata?.name || "Unnamed invite";
    const role = invitation.spec?.role ?? IamRole.iam_role_unspecified;
    const state = invitation.status?.state ?? InvitationState.invitation_state_unspecified;
    const token = invitation.status?.token ?? "";
    const redemptionCount = invitation.status?.redemptionCount ?? 0;
    const maxRedemptions = invitation.spec?.maxRedemptions ?? 0;
    const expiresAt = invitation.spec?.expiresAt;
    const isActive = state === InvitationState.active;
    const handleCopy = useCallback(async () => {
        try {
            await navigator.clipboard.writeText(buildInviteUrl(token));
            setCopied(true);
            setTimeout(() => setCopied(false), 2000);
        }
        catch {
            // silently fail — copy is a convenience, not critical
        }
    }, [token, buildInviteUrl]);
    if (isRevoking) {
        return (_jsx(RevokeConfirmation, { invitationId: id, label: label, onRevoked: onRevoked, onCancel: onCancelRevoke }));
    }
    return (_jsxs("div", { role: "listitem", className: "flex items-center gap-3 rounded-lg border border-border-muted px-3 py-2.5 hover:border-border transition-colors", children: [_jsx(LinkIcon, { active: isActive }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("span", { className: cn("block truncate text-sm font-medium", isActive ? "text-foreground" : "text-muted-foreground"), children: label }), _jsx("span", { className: "block text-xs text-muted-foreground", children: formatRedemptions(redemptionCount, maxRedemptions) })] }), _jsxs("div", { className: "hidden sm:flex shrink-0 items-center gap-3", children: [_jsx("span", { className: "inline-flex items-center rounded-md border border-border bg-muted px-2 py-0.5 text-[0.65rem] font-medium text-foreground", children: iamRoleDisplayName(role) }), _jsx(StateBadge, { state: state }), expiresAt && (_jsx("span", { className: "text-xs text-muted-foreground", title: timestampDate(expiresAt).toISOString(), children: isActive
                            ? formatRelativeExpiry(timestampDate(expiresAt))
                            : formatShortDate(timestampDate(expiresAt)) }))] }), _jsxs("div", { className: "flex shrink-0 items-center gap-1", children: [isActive && (_jsx("button", { type: "button", onClick: handleCopy, "aria-label": copied ? "Link copied" : `Copy invite link for ${label}`, className: cn("shrink-0 rounded p-1 transition-colors", copied
                            ? "text-primary"
                            : "text-muted-foreground hover:text-foreground hover:bg-accent-hover"), children: copied ? _jsx(CheckIcon, {}) : _jsx(CopyIcon, {}) })), isActive && (_jsx("button", { type: "button", onClick: onStartRevoke, "aria-label": `Revoke ${label}`, className: "shrink-0 rounded p-1 text-muted-foreground hover:text-destructive hover:bg-destructive-subtle transition-colors", children: _jsx(RevokeIcon, {}) }))] })] }));
}
// ---------------------------------------------------------------------------
// RevokeConfirmation (internal)
// ---------------------------------------------------------------------------
function RevokeConfirmation({ invitationId, label, onRevoked, onCancel, }) {
    const { revoke, isRevoking, error } = useRevokeInvitation();
    const handleConfirm = useCallback(async () => {
        try {
            await revoke(invitationId);
            onRevoked();
        }
        catch {
            // error state is surfaced via the hook
        }
    }, [invitationId, revoke, onRevoked]);
    return (_jsxs("div", { role: "listitem", className: "flex items-center justify-between rounded-lg border border-destructive/30 bg-destructive-subtle px-3 py-2.5", children: [_jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("p", { className: "text-xs text-foreground", children: ["Revoke ", _jsx("span", { className: "font-medium", children: label }), "? The link will stop working immediately."] }), error && (_jsx("p", { className: "mt-0.5 text-[0.65rem] text-destructive", children: getUserMessage(error) }))] }), _jsxs("div", { className: "flex shrink-0 items-center gap-1.5 ml-3", children: [_jsxs("button", { type: "button", onClick: handleConfirm, disabled: isRevoking, className: cn("inline-flex items-center gap-1 rounded-md px-2.5 py-1 text-xs font-medium", "bg-destructive text-destructive-foreground hover:bg-destructive-hover", "disabled:pointer-events-none disabled:opacity-50"), children: [isRevoking && _jsx(SpinnerIcon, {}), "Revoke"] }), _jsx("button", { type: "button", onClick: onCancel, disabled: isRevoking, className: cn("rounded-md px-2.5 py-1 text-xs", "text-muted-foreground hover:text-foreground hover:bg-accent-hover", "disabled:pointer-events-none disabled:opacity-50"), children: "Cancel" })] })] }));
}
// ---------------------------------------------------------------------------
// StateBadge (internal)
// ---------------------------------------------------------------------------
function StateBadge({ state }) {
    const config = STATE_BADGE_CONFIG[state] ?? STATE_BADGE_CONFIG[InvitationState.invitation_state_unspecified];
    return (_jsx("span", { className: cn("inline-flex items-center rounded-full px-2 py-0.5 text-[0.6rem] font-medium", config.className), children: config.label }));
}
const STATE_BADGE_CONFIG = {
    [InvitationState.invitation_state_unspecified]: {
        label: "Unknown",
        className: "bg-muted text-muted-foreground",
    },
    [InvitationState.active]: {
        label: "Active",
        className: "bg-primary-subtle text-primary",
    },
    [InvitationState.expired]: {
        label: "Expired",
        className: "bg-muted text-muted-foreground",
    },
    [InvitationState.revoked]: {
        label: "Revoked",
        className: "bg-muted text-muted-foreground",
    },
    [InvitationState.fully_redeemed]: {
        label: "Fully redeemed",
        className: "bg-muted text-muted-foreground",
    },
};
// ---------------------------------------------------------------------------
// Radio helpers (internal)
// ---------------------------------------------------------------------------
function ExpiryRadio({ value, label, checked, disabled, onChange, }) {
    return (_jsxs("label", { className: cn("inline-flex cursor-pointer items-center rounded-md border px-2.5 py-1 text-xs transition-colors", checked
            ? "border-primary bg-primary-subtle text-primary font-medium"
            : "border-input bg-background text-muted-foreground hover:border-border hover:text-foreground", disabled && "pointer-events-none opacity-50"), children: [_jsx("input", { type: "radio", name: "stgm-invite-expiry", value: value, checked: checked, disabled: disabled, onChange: () => onChange(value), className: "sr-only" }), label] }));
}
function RedemptionRadio({ value, label, description, checked, disabled, onChange, }) {
    return (_jsxs("label", { className: cn("inline-flex cursor-pointer flex-col rounded-md border px-3 py-1.5 text-xs transition-colors", checked
            ? "border-primary bg-primary-subtle text-primary font-medium"
            : "border-input bg-background text-muted-foreground hover:border-border hover:text-foreground", disabled && "pointer-events-none opacity-50"), children: [_jsx("input", { type: "radio", name: "stgm-invite-redemption", value: value, checked: checked, disabled: disabled, onChange: () => onChange(value), className: "sr-only" }), _jsx("span", { children: label }), _jsx("span", { className: "text-[0.625rem] text-muted-foreground font-normal mt-0.5", children: description })] }));
}
// ---------------------------------------------------------------------------
// Formatting helpers
// ---------------------------------------------------------------------------
function daysFromNow(days) {
    const date = new Date();
    date.setDate(date.getDate() + days);
    return date;
}
function formatRedemptions(count, max) {
    if (max === 0) {
        return count === 0 ? "No redemptions yet" : `${count} redeemed`;
    }
    return `${count} / ${max} redeemed`;
}
function formatRelativeExpiry(date) {
    const now = Date.now();
    const diffMs = date.getTime() - now;
    if (diffMs <= 0)
        return "Expired";
    const days = Math.ceil(diffMs / 86_400_000);
    if (days === 1)
        return "Expires tomorrow";
    if (days <= 30)
        return `Expires in ${days} days`;
    return `Expires ${formatShortDate(date)}`;
}
function formatShortDate(date) {
    return date.toLocaleDateString(undefined, {
        month: "short",
        day: "numeric",
        year: "numeric",
    });
}
// ---------------------------------------------------------------------------
// Icons
// ---------------------------------------------------------------------------
function LinkIcon({ active }) {
    return (_jsxs("svg", { width: "14", height: "14", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", className: cn("shrink-0", active ? "text-primary" : "text-muted-foreground"), children: [_jsx("path", { d: "M6.5 9.5a3.5 3.5 0 0 0 5 0l2-2a3.5 3.5 0 0 0-5-5l-1 1" }), _jsx("path", { d: "M9.5 6.5a3.5 3.5 0 0 0-5 0l-2 2a3.5 3.5 0 0 0 5 5l1-1" })] }));
}
function CopyIcon() {
    return (_jsxs("svg", { width: "14", height: "14", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [_jsx("rect", { x: "5", y: "5", width: "9", height: "9", rx: "1.5" }), _jsx("path", { d: "M5 11H3.5A1.5 1.5 0 0 1 2 9.5V3.5A1.5 1.5 0 0 1 3.5 2h6A1.5 1.5 0 0 1 11 3.5V5" })] }));
}
function CheckIcon() {
    return (_jsx("svg", { width: "14", height: "14", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: _jsx("path", { d: "M3 8.5l3.5 3.5L13 5" }) }));
}
function RevokeIcon() {
    return (_jsxs("svg", { width: "14", height: "14", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [_jsx("circle", { cx: "8", cy: "8", r: "6" }), _jsx("path", { d: "M4.5 11.5l7-7" })] }));
}
function SpinnerIcon() {
    return (_jsx("svg", { width: "12", height: "12", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", className: "animate-spin", "aria-hidden": "true", children: _jsx("path", { d: "M8 2a6 6 0 1 0 6 6" }) }));
}
//# sourceMappingURL=InvitationManager.js.map