"use client";
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { memo, useCallback, useEffect, useMemo, useState } from "react";
import { ApprovalAction, ToolKind } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
import { cn } from "@stigmer/theme";
import { describeApprovalPolicySource, isInformativePolicySource, } from "./approval-provenance.js";
import { resolveToolCategoryFromKind, extractPrimaryArgFromPreview, extractWriteContentFromPreview, isFileCategory, } from "./tool-categories.js";
import { CATEGORY_ICON } from "./ToolCallItem.js";
import { ToolArgsView } from "./ToolArgsView.js";
import { EmptyChangeNotice } from "./EmptyChangeNotice.js";
import { FilePathLink } from "./FilePathLink.js";
import { DecisionButton } from "../internal/DecisionButton.js";
import { InCardDecisionError } from "../internal/InCardDecisionError.js";
import { useElapsedSince, formatElapsed } from "../internal/useElapsedSince.js";
/**
 * Renders a pending tool-call approval request with the same visual
 * structure as an expanded {@link ToolCallItem} in the history list.
 *
 * The compact header row matches the ToolCallItem layout:
 *   [CategoryIcon] Label  primaryArg  [⏳ waiting Xm]
 *
 * The body shows the proposed change from the tool args (the
 * pre-execution deny-gate has no captured `FileChangeSet` yet): the
 * write/edit content when present, an {@link EmptyChangeNotice} when
 * only a path is known, otherwise the shared {@link ToolArgsView}
 * dispatch, keeping pixel-level parity with the detail view.
 *
 * Wrapped in `React.memo` — structural sharing (T04) preserves the
 * `PendingApproval` reference when unchanged, so approval cards
 * skip re-renders during unrelated stream updates.
 *
 * @example
 * ```tsx
 * <ApprovalCard
 *   pendingApproval={approval}
 *   onSubmit={(action) => submitApproval(executionId, approval.toolCallId, action)}
 *   isSubmitting={submittingIds.has(approval.toolCallId)}
 * />
 * ```
 */
export const ApprovalCard = memo(function ApprovalCard({ pendingApproval, onSubmit, isSubmitting = false, error = null, className, }) {
    // Prefer the denormalized wire tool_kind (populated by the server-side
    // PendingApproval projection); fall back to the name for legacy executions.
    const categoryInfo = resolveToolCategoryFromKind(pendingApproval.toolKind, pendingApproval.toolName, pendingApproval.mcpServerSlug);
    // Cursor-grade chrome: a thin neutral card (no amber "brown" fill), with the
    // gate's "needs your decision" signal carried by one restrained cue — a 2px
    // left accent bar — plus the warning-colored clock/elapsed in the header. A
    // destructive (delete) gate keeps a red accent as a hard safety signal.
    const accentClass = categoryInfo.category === "delete"
        ? "border-l-2 border-l-destructive"
        : "border-l-2 border-l-warning";
    return (_jsxs("div", { role: "alert", "aria-label": `Approval required for ${pendingApproval.toolName}`, className: cn("rounded-lg border border-border-prominent", accentClass, className), children: [_jsx(ApprovalCardHeader, { pendingApproval: pendingApproval, bordered: true }), _jsx(ApprovalCardBody, { pendingApproval: pendingApproval, onSubmit: onSubmit, isSubmitting: isSubmitting, error: error })] }));
});
/**
 * The compact summary row of an approval — `[icon] Label primaryArg [via sub]
 * [⏳ Xm]`. Used as the standalone {@link ApprovalCard}'s header. Not rendered
 * inline on a {@link ToolCallItem}, whose own row already serves as the header.
 */
export function ApprovalCardHeader({ pendingApproval, bordered = false, }) {
    const categoryInfo = resolveToolCategoryFromKind(pendingApproval.toolKind, pendingApproval.toolName, pendingApproval.mcpServerSlug);
    const CategoryIcon = CATEGORY_ICON[categoryInfo.category];
    const primaryArg = useMemo(() => extractPrimaryArgFromPreview(pendingApproval.toolName, pendingApproval.argsPreview, pendingApproval.mcpServerSlug, pendingApproval.toolKind), [
        pendingApproval.toolName,
        pendingApproval.argsPreview,
        pendingApproval.mcpServerSlug,
        pendingApproval.toolKind,
    ]);
    return (_jsxs("div", { className: cn("flex items-center gap-2 px-2.5 py-1.5 text-xs", bordered && "border-b border-border-muted"), children: [_jsx("span", { className: "shrink-0 text-warning", "aria-hidden": "true", children: _jsx(CategoryIcon, {}) }), _jsxs("span", { className: "min-w-0 flex-1 flex items-center gap-1.5 overflow-hidden", children: [_jsx("span", { className: "shrink-0 font-medium text-foreground", children: categoryInfo.label }), primaryArg &&
                        categoryInfo.category !== "shell" &&
                        (isFileCategory(categoryInfo.category) ? (
                        // A file tool's primary arg is a path — render it filename-first
                        // with the full path on hover, matching the ToolCallItem header.
                        _jsx(FilePathLink, { path: primaryArg, className: "min-w-0 text-xs text-muted-foreground" })) : (
                        // Shell is the exception: its command is shown in the body's
                        // terminal session, so the header stays minimal (icon + label).
                        _jsx("span", { className: "min-w-0 truncate font-mono text-muted-foreground", children: primaryArg })))] }), pendingApproval.fromSubAgent && pendingApproval.subAgentName && (_jsxs("span", { className: "shrink-0 rounded bg-muted px-1 py-0.5 font-mono text-muted-foreground", children: ["via ", pendingApproval.subAgentSubject || pendingApproval.subAgentName] })), _jsx(WaitingDuration, { requestedAt: pendingApproval.requestedAt }), _jsx("span", { className: "shrink-0 text-warning", "aria-hidden": "true", children: _jsx(ClockIcon, {}) })] }));
}
/**
 * The decision panel of an approval: the optional message and gate reason, the
 * file-changes / args preview, and the action buttons. Reused in two places —
 * the standalone {@link ApprovalCard} (with {@link ApprovalCardHeader} above
 * it) and inline inside a gated {@link ToolCallItem}'s expanded panel, where
 * the item's own row is the header. One body, identical preview in both.
 */
export function ApprovalCardBody({ pendingApproval, onSubmit, isSubmitting = false, error = null, className, }) {
    const [activeAction, setActiveAction] = useState(null);
    const handleAction = useCallback((action) => {
        setActiveAction(action);
        onSubmit(action);
    }, [onSubmit]);
    useEffect(() => {
        if (!isSubmitting) {
            setActiveAction(null);
        }
    }, [isSubmitting]);
    const categoryInfo = resolveToolCategoryFromKind(pendingApproval.toolKind, pendingApproval.toolName, pendingApproval.mcpServerSlug);
    const parsedArgs = useMemo(() => {
        if (!pendingApproval.argsPreview)
            return null;
        try {
            const parsed = JSON.parse(pendingApproval.argsPreview);
            if (typeof parsed === "object" && parsed !== null) {
                return parsed;
            }
            return null;
        }
        catch {
            return null;
        }
    }, [pendingApproval.argsPreview]);
    // APPROVE_ALL now grants a run-lifetime lease scoped to the clicked tool's
    // CLASS — its MCP server for an MCP tool, else its approval category (where
    // write and edit collapse to one "file edits" class, mirroring the runner's
    // toolApprovalCategory). The label must name that exact class so the button
    // never over-promises: it does NOT silence other classes.
    const approveAllLabel = useMemo(() => buildApproveAllLabel(categoryInfo.category, pendingApproval.mcpServerSlug), [categoryInfo.category, pendingApproval.mcpServerSlug]);
    // Why-gated: the authorization provenance the server projected onto the
    // PendingApproval (approval_policy_source). Smart-suppressed — the everyday
    // "this category needs approval" default is noise next to the action it gates,
    // so only a genuinely informative provenance (an explicit override, a
    // server-marked destructive tighten) earns a line; the full phrase is on hover.
    const gateReason = useMemo(() => describeApprovalPolicySource(pendingApproval.approvalPolicySource), [pendingApproval.approvalPolicySource]);
    const showGateReason = gateReason != null &&
        isInformativePolicySource(pendingApproval.approvalPolicySource);
    // The runner's message for a file tool ("Write file: <path>") only restates
    // the header + diff, so it is suppressed; a message is shown only when it adds
    // information (e.g. an MCP tool's human-authored prompt). Shell shows its
    // command in the args view, never a message.
    const showMessage = Boolean(pendingApproval.message) &&
        categoryInfo.category !== "shell" &&
        !isFileCategory(categoryInfo.category);
    const isWriteEdit = categoryInfo.category === "write" || categoryInfo.category === "edit";
    // The proposed write/edit content from the args preview, when the gate carries
    // no authoritative file_changes capture. Drives the three-way fallback below.
    const writeContent = useMemo(() => extractWriteContentFromPreview(pendingApproval.argsPreview), [pendingApproval.argsPreview]);
    // The decision-relevant preview. The pre-execution deny-gate is an
    // apply-then-review exception: the change has not been captured yet (the tool
    // is DENIED, awaiting approval), so there is no `FileChangeSet` to render.
    // Instead the gate shows the PROPOSED change from the tool args: the write/edit
    // content when the args carry it (path suppressed — the header has it); a
    // neutral "no preview" notice when only a path is known (the resume placeholder
    // case); otherwise the shared args view for non-file tools. (Captured
    // file-review — the apply-then-review path — renders via FileReviewCard, not
    // here.)
    let preview = null;
    if (isWriteEdit && writeContent === null) {
        // No proposed content in the args. A whole-file write is modeled as a create
        // throughout the runner (FILE_WRITE -> CREATE), so the authoritative toolKind
        // lets the gate say plainly that a new file is being written rather than the
        // misleading non-committal "no preview". An edit (modify) cannot be proven a
        // create, so it keeps the non-committal notice.
        const kind = pendingApproval.toolKind === ToolKind.FILE_WRITE ? "create" : "no-preview";
        preview = _jsx(EmptyChangeNotice, { kind: kind });
    }
    else if (parsedArgs) {
        preview = (_jsx(ToolArgsView, { toolName: pendingApproval.toolName, args: parsedArgs, mcpServerSlug: pendingApproval.mcpServerSlug, showFileName: !isWriteEdit }));
    }
    return (_jsxs("div", { className: cn("px-3 py-2.5 space-y-2", className), children: [showMessage && (_jsx("p", { className: "text-xs text-foreground", children: pendingApproval.message })), preview, categoryInfo.category === "shell" && (_jsx("p", { className: "text-[11px] italic text-muted-foreground", children: "Files this command creates or changes are covered by this approval \u2014 they are kept automatically, with no second review." })), _jsxs("div", { className: "flex items-center gap-2 pt-1", children: [_jsx(DecisionButton, { label: "Approve", variant: "primary", onClick: () => handleAction(ApprovalAction.APPROVE), isActive: activeAction === ApprovalAction.APPROVE, isSubmitting: isSubmitting, cursorTarget: "approve-button" }), _jsx(DecisionButton, { label: "Skip", variant: "ghost", onClick: () => handleAction(ApprovalAction.SKIP), isActive: activeAction === ApprovalAction.SKIP, isSubmitting: isSubmitting }), _jsx(DecisionButton, { label: "Reject", variant: "danger", onClick: () => handleAction(ApprovalAction.REJECT), isActive: activeAction === ApprovalAction.REJECT, isSubmitting: isSubmitting }), _jsx(DecisionButton, { label: approveAllLabel, variant: "ghost", onClick: () => handleAction(ApprovalAction.APPROVE_ALL), isActive: activeAction === ApprovalAction.APPROVE_ALL, isSubmitting: isSubmitting, className: "ml-auto", cursorTarget: "approve-all-button" })] }), showGateReason && (_jsx("p", { className: "text-[11px] italic text-muted-foreground", title: gateReason ?? undefined, "data-cursor-target": "approval-gate-reason", children: gateReason })), error && (_jsx(InCardDecisionError, { error: error, leadIn: "submit decision", cursorTarget: "approval-error" }))] }));
}
// ---------------------------------------------------------------------------
// Internal helpers
// ---------------------------------------------------------------------------
/**
 * Builds the truthful APPROVE_ALL button label for a tool's lease class.
 *
 * The lease an APPROVE_ALL grants is scoped to ONE class: the MCP server for an
 * MCP tool, otherwise the approval category (write/delete/shell) — where the
 * presentation "write" and "edit" categories collapse to a single "file edits"
 * class, exactly as the runner's `toolApprovalCategory` collapses FILE_WRITE and
 * FILE_EDIT to "write". The label names that class so the button never implies a
 * broader effect than the lease actually has. The generic fallback only applies
 * to a tool with no leasable class, which is not normally gated for approval.
 */
function buildApproveAllLabel(category, mcpServerSlug) {
    if (mcpServerSlug) {
        return `Approve all ${mcpServerSlug} tools`;
    }
    switch (category) {
        case "shell":
            return "Approve all shell commands";
        case "delete":
            return "Approve all file deletions";
        case "write":
        case "edit":
            return "Approve all file edits";
        default:
            return "Approve all of this kind";
    }
}
// ---------------------------------------------------------------------------
// Internal sub-components
// ---------------------------------------------------------------------------
function WaitingDuration({ requestedAt }) {
    const elapsed = useElapsedSince(requestedAt);
    if (elapsed === null)
        return null;
    return (_jsx("span", { className: "shrink-0 text-xs text-muted-foreground", children: formatElapsed(elapsed) }));
}
// ---------------------------------------------------------------------------
// Inline SVG icons
// ---------------------------------------------------------------------------
function ClockIcon() {
    return (_jsxs("svg", { width: "10", height: "10", viewBox: "0 0 12 12", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("circle", { cx: "6", cy: "6", r: "4.5" }), _jsx("path", { d: "M6 3.5V6L7.5 7.5" })] }));
}
//# sourceMappingURL=ApprovalCard.js.map