"use client";
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import { useCallback, useEffect, useRef, useState } from "react";
import { cn } from "@stigmer/theme";
import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
import { ApiResourceVisibility } from "@stigmer/protos/ai/stigmer/commons/apiresource/enum_pb";
import { ScopeToggle } from "./ScopeToggle";
const DEBOUNCE_MS = 300;
const SKELETON_COUNT = 5;
const GRID_SKELETON_COUNT = 6;
const MAX_VISIBLE_TAGS = 3;
/**
 * Paginated, searchable view for browsing Stigmer resources.
 *
 * Supports two layout modes:
 *
 * - **`"list"`** (default) — vertical single-column rows, same as
 *   before. Each row shows a kind icon, name, org, description, and tags.
 * - **`"grid"`** — responsive multi-column card grid
 *   (`grid-cols-1 sm:grid-cols-2 lg:grid-cols-3`). Each card shows a
 *   large icon container, name, org, description, and an optional
 *   action slot in the top-right corner.
 *
 * Both modes share the same toolbar (search, scope toggle), pagination,
 * loading/error/empty states, and keyboard navigation (grid mode adds
 * ArrowLeft/Right and column-aware Up/Down).
 *
 * The component is controlled — the parent owns data and filter state.
 * Search debouncing (300ms) is managed internally so the parent only
 * receives debounced query values via {@link ResourceListViewProps.onSearchChange}.
 *
 * Only `items` and `isLoading` are required. Search, scope toggle,
 * pagination, layout mode, and custom row rendering activate
 * progressively when their corresponding props are provided.
 *
 * When the debounced search query or scope changes, the component
 * automatically resets the page to 1 via `onPageChange` to prevent
 * stale pagination after filter changes.
 *
 * @example
 * ```tsx
 * // Minimal — plain list with loading indicator
 * <ResourceListView items={agents} isLoading={isLoading} />
 * ```
 *
 * @example
 * ```tsx
 * // Grid layout with action button
 * <ResourceListView
 *   layout="grid"
 *   items={mcpServers}
 *   isLoading={isLoading}
 *   onItemClick={(item) => navigate(item.slug)}
 *   renderItemAction={(item) => (
 *     <button onClick={(e) => { e.stopPropagation(); connect(item); }}>
 *       <PlusIcon />
 *     </button>
 *   )}
 * />
 * ```
 *
 * @example
 * ```tsx
 * // Full — search, scope toggle, pagination, and click handling
 * const [scope, setScope] = useState<ResourceListScope>("org");
 * const [query, setQuery] = useState("");
 * const [page, setPage] = useState(1);
 * const { agents, totalCount, totalPages, isLoading, error, refetch } =
 *   useAgentList(org, { query, scope, page });
 *
 * <ResourceListView
 *   items={agents}
 *   isLoading={isLoading}
 *   error={error}
 *   totalCount={totalCount}
 *   totalPages={totalPages}
 *   currentPage={page}
 *   onSearchChange={setQuery}
 *   searchPlaceholder="Search agents\u2026"
 *   scope={scope}
 *   onScopeChange={setScope}
 *   onPageChange={setPage}
 *   onItemClick={(item) => console.log(item.slug)}
 *   onRetry={refetch}
 * />
 * ```
 *
 * @see {@link useAgentList}, {@link useSkillList}, {@link useMcpServerList}
 * @see {@link ScopeToggle} for the scope toggle used in the toolbar
 */
export function ResourceListView({ items, isLoading, error, totalCount, totalPages = 1, currentPage = 1, onSearchChange, searchPlaceholder = "Search\u2026", initialSearch = "", scope, onScopeChange, onPageChange, layout = "list", renderItem, renderItemAction, onItemClick, emptyIcon, emptyTitle = "No resources found", emptyDescription = "Try adjusting your search or scope.", onRetry, className, "aria-label": ariaLabel = "Resource list", }) {
    const isGrid = layout === "grid";
    const showToolbar = !!onSearchChange || (scope !== undefined && !!onScopeChange);
    const showPagination = !!onPageChange && totalPages > 1;
    const isInteractive = !!onItemClick;
    // --- Search debounce ------------------------------------------------
    // The raw input value lives here; the parent only sees debounced values.
    const [inputValue, setInputValue] = useState(initialSearch);
    const isFirstRender = useRef(true);
    // Stable callback refs so the debounce timer is not reset when
    // the parent passes new callback identities on re-render.
    const onSearchChangeRef = useRef(onSearchChange);
    onSearchChangeRef.current = onSearchChange;
    const onPageChangeRef = useRef(onPageChange);
    onPageChangeRef.current = onPageChange;
    useEffect(() => {
        if (isFirstRender.current) {
            isFirstRender.current = false;
            return;
        }
        if (!onSearchChangeRef.current)
            return;
        const timer = setTimeout(() => {
            onSearchChangeRef.current?.(inputValue);
            onPageChangeRef.current?.(1);
        }, DEBOUNCE_MS);
        return () => clearTimeout(timer);
    }, [inputValue]);
    // --- Scope change resets page ---------------------------------------
    const handleScopeChange = useCallback((newScope) => {
        onScopeChange?.(newScope);
        onPageChange?.(1);
    }, [onScopeChange, onPageChange]);
    // --- Keyboard navigation for interactive items ----------------------
    // Implements roving tabindex: only the focused item has tabIndex 0,
    // all others have -1. Arrow keys move focus between items.
    // In list mode: Up/Down. In grid mode: all four arrow keys with
    // column-aware wrapping.
    const itemRefs = useRef([]);
    const gridColumnsRef = useRef(1);
    const moveFocus = useCallback((from, toIndex) => {
        const clamped = Math.max(0, Math.min(toIndex, items.length - 1));
        const el = itemRefs.current[clamped];
        if (el && el !== from) {
            from.tabIndex = -1;
            el.tabIndex = 0;
            el.focus();
        }
    }, [items.length]);
    const detectGridColumns = useCallback(() => {
        if (!isGrid)
            return 1;
        const first = itemRefs.current[0];
        const second = itemRefs.current[1];
        if (!first || !second)
            return 1;
        const firstRect = first.getBoundingClientRect();
        const secondRect = second.getBoundingClientRect();
        if (Math.abs(firstRect.top - secondRect.top) < 4) {
            let cols = 1;
            for (let i = 1; i < itemRefs.current.length; i++) {
                const r = itemRefs.current[i]?.getBoundingClientRect();
                if (r && Math.abs(r.top - firstRect.top) < 4) {
                    cols++;
                }
                else {
                    break;
                }
            }
            return cols;
        }
        return 1;
    }, [isGrid]);
    const handleItemKeyDown = useCallback((e, index, item) => {
        if (e.key === "Enter" || e.key === " ") {
            e.preventDefault();
            onItemClick?.(item);
            return;
        }
        const cols = isGrid ? detectGridColumns() : 1;
        gridColumnsRef.current = cols;
        let target = index;
        switch (e.key) {
            case "ArrowDown":
                e.preventDefault();
                target = isGrid ? Math.min(index + cols, items.length - 1) : Math.min(index + 1, items.length - 1);
                break;
            case "ArrowUp":
                e.preventDefault();
                target = isGrid ? Math.max(index - cols, 0) : Math.max(index - 1, 0);
                break;
            case "ArrowRight":
                if (isGrid) {
                    e.preventDefault();
                    target = Math.min(index + 1, items.length - 1);
                }
                break;
            case "ArrowLeft":
                if (isGrid) {
                    e.preventDefault();
                    target = Math.max(index - 1, 0);
                }
                break;
            default:
                return;
        }
        if (target !== index)
            moveFocus(e.currentTarget, target);
    }, [onItemClick, items.length, isGrid, detectGridColumns, moveFocus]);
    // --- Content resolution ---------------------------------------------
    const showSkeletons = isLoading && items.length === 0;
    const showError = !!error && !isLoading;
    const showEmpty = !isLoading && !error && items.length === 0;
    const showItems = items.length > 0;
    return (_jsxs("div", { className: cn("flex flex-col gap-3", className), children: [showToolbar && (_jsxs("div", { role: "search", className: "flex items-center gap-2", children: [onSearchChange && (_jsxs("div", { className: "relative flex-1", children: [_jsx(SearchIcon, { className: "pointer-events-none absolute left-2.5 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground-subtle" }), _jsx("input", { type: "text", value: inputValue, onChange: (e) => setInputValue(e.target.value), placeholder: searchPlaceholder, "aria-label": searchPlaceholder, className: cn("w-full rounded-md border border-input bg-background py-1.5 pl-8 pr-3 text-sm", "placeholder:text-muted-foreground-subtle", "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring") })] })), scope !== undefined && onScopeChange && (_jsx(ScopeToggle, { value: scope, onChange: handleScopeChange }))] })), showSkeletons && (isGrid ? _jsx(SkeletonCards, {}) : _jsx(SkeletonRows, {})), showError && _jsx(ErrorState, { message: error.message, onRetry: onRetry }), showEmpty && (_jsx(EmptyState, { icon: emptyIcon, title: emptyTitle, description: emptyDescription })), showItems && (_jsx("div", { role: "list", "aria-label": ariaLabel, "aria-busy": isLoading || undefined, className: cn(isGrid
                    ? "grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3"
                    : "flex flex-col", isLoading &&
                    "pointer-events-none opacity-60 transition-opacity"), children: items.map((item, index) => {
                    const content = renderItem
                        ? renderItem(item, index)
                        : isGrid
                            ? _jsx(DefaultResourceCard, { item: item, action: renderItemAction?.(item) })
                            : _jsx(DefaultResourceRow, { item: item, action: renderItemAction?.(item) });
                    return (_jsx("div", { role: "listitem", children: isInteractive ? (_jsx("div", { ref: (el) => {
                                itemRefs.current[index] = el;
                            }, role: "button", tabIndex: index === 0 ? 0 : -1, onClick: () => onItemClick(item), onKeyDown: (e) => handleItemKeyDown(e, index, item), className: cn("group transition-colors", isGrid
                                ? [
                                    "flex h-full rounded-lg border border-border bg-card p-4",
                                    "cursor-pointer hover:border-primary/40 hover:bg-accent-hover",
                                    "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
                                ]
                                : [
                                    "rounded-lg px-3 py-2.5",
                                    "cursor-pointer hover:bg-accent-hover",
                                    "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring",
                                ]), children: content })) : (_jsx("div", { className: cn(isGrid
                                ? "flex h-full rounded-lg border border-border bg-card p-4"
                                : "px-3 py-2.5"), children: content })) }, item.id || `resource-${index}`));
                }) })), showPagination && (_jsx(PaginationBar, { currentPage: currentPage, totalPages: totalPages, totalCount: totalCount, onPageChange: onPageChange }))] }));
}
// ---------------------------------------------------------------------------
// Internal components
// ---------------------------------------------------------------------------
function DefaultResourceRow({ item, action, }) {
    const displayName = item.name || item.slug;
    return (_jsxs("div", { className: "flex items-start gap-3", children: [_jsx(RowIcon, { kind: item.kind, iconUrl: item.iconUrl }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "truncate text-sm font-medium text-foreground", children: displayName }), item.visibility === ApiResourceVisibility.visibility_public && (_jsx(VisibilityBadge, {}))] }), _jsxs("div", { className: "mt-0.5 flex items-start gap-1.5 text-xs text-muted-foreground", children: [_jsx("span", { className: "shrink-0", children: item.org }), item.description && (_jsxs(_Fragment, { children: [_jsx("span", { className: "shrink-0", "aria-hidden": "true", children: "\u00B7" }), _jsx("span", { className: "line-clamp-2 group-hover:line-clamp-none", children: item.description })] }))] }), item.tags.length > 0 && (_jsxs("div", { className: "mt-1.5 flex flex-wrap gap-1", children: [item.tags.slice(0, MAX_VISIBLE_TAGS).map((tag) => (_jsx("span", { className: "rounded bg-muted px-1.5 py-0.5 text-[10px] text-muted-foreground", children: tag }, tag))), item.tags.length > MAX_VISIBLE_TAGS && (_jsxs("span", { className: "text-[10px] text-muted-foreground-subtle", children: ["+", item.tags.length - MAX_VISIBLE_TAGS, " more"] }))] }))] }), action && _jsx("div", { className: "ml-auto shrink-0", children: action })] }));
}
function DefaultResourceCard({ item, action, }) {
    const displayName = item.name || item.slug;
    return (_jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-3", children: [_jsxs("div", { className: "flex items-start gap-3", children: [_jsx(ResourceIcon, { kind: item.kind, iconUrl: item.iconUrl }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("span", { className: "line-clamp-2 text-sm font-semibold leading-snug text-foreground", children: displayName }), _jsx("span", { className: "mt-0.5 block text-xs text-muted-foreground", children: item.org })] }), action && _jsx("div", { className: "shrink-0", children: action })] }), item.description && (_jsx("p", { className: "line-clamp-2 text-xs leading-relaxed text-muted-foreground", children: item.description })), item.visibility === ApiResourceVisibility.visibility_public && (_jsx("div", { className: "mt-auto", children: _jsx(VisibilityBadge, {}) }))] }));
}
function ResourceIcon({ kind, iconUrl, }) {
    const [imgError, setImgError] = useState(false);
    return (_jsx("span", { className: "flex size-10 shrink-0 items-center justify-center rounded-lg bg-muted", children: iconUrl && !imgError ? (_jsx("img", { src: iconUrl, alt: "", className: "size-6 rounded object-contain", onError: () => setImgError(true) })) : (_jsx(KindIcon, { kind: kind, size: "lg" })) }));
}
function RowIcon({ kind, iconUrl, }) {
    const [imgError, setImgError] = useState(false);
    if (iconUrl && !imgError) {
        return (_jsx("img", { src: iconUrl, alt: "", className: "mt-0.5 h-4 w-4 shrink-0 rounded-sm object-contain", onError: () => setImgError(true) }));
    }
    return _jsx(KindIcon, { kind: kind });
}
function VisibilityBadge() {
    return (_jsx("span", { className: "shrink-0 rounded-full bg-muted px-2 py-0.5 text-[10px] font-medium text-muted-foreground", children: "Public" }));
}
function KindIcon({ kind, size = "sm", }) {
    const cls = size === "lg"
        ? "h-5 w-5 shrink-0 text-muted-foreground"
        : "mt-0.5 h-4 w-4 shrink-0 text-muted-foreground";
    switch (kind) {
        case ApiResourceKind.agent:
            return _jsx(AgentIcon, { className: cls });
        case ApiResourceKind.skill:
            return _jsx(SkillIcon, { className: cls });
        case ApiResourceKind.mcp_server:
            return _jsx(McpServerIcon, { className: cls });
        case ApiResourceKind.workflow:
            return _jsx(WorkflowIcon, { className: cls });
        default:
            return _jsx(DocumentIcon, { className: cls });
    }
}
function SkeletonRows() {
    const widths = [36, 45, 30, 52, 40];
    return (_jsx("div", { "aria-busy": "true", children: Array.from({ length: SKELETON_COUNT }, (_, i) => (_jsxs("div", { className: "flex items-start gap-3 px-3 py-2.5", "aria-hidden": "true", children: [_jsx("div", { className: "mt-0.5 h-4 w-4 shrink-0 animate-pulse rounded bg-muted" }), _jsxs("div", { className: "flex-1 space-y-2", children: [_jsx("div", { className: "h-4 animate-pulse rounded bg-muted", style: { width: `${widths[i]}%` } }), _jsx("div", { className: "h-3 animate-pulse rounded bg-muted", style: { width: `${widths[i] + 25}%` } })] })] }, i))) }));
}
function SkeletonCards() {
    return (_jsx("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3", "aria-busy": "true", children: Array.from({ length: GRID_SKELETON_COUNT }, (_, i) => (_jsxs("div", { className: "flex flex-col gap-3 rounded-lg border border-border bg-card p-4", "aria-hidden": "true", children: [_jsxs("div", { className: "flex items-start gap-3", children: [_jsx("div", { className: "size-10 shrink-0 animate-pulse rounded-lg bg-muted" }), _jsxs("div", { className: "flex-1 space-y-2", children: [_jsx("div", { className: "h-4 w-3/5 animate-pulse rounded bg-muted" }), _jsx("div", { className: "h-3 w-2/5 animate-pulse rounded bg-muted" })] })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx("div", { className: "h-3 w-full animate-pulse rounded bg-muted" }), _jsx("div", { className: "h-3 w-4/5 animate-pulse rounded bg-muted" })] })] }, i))) }));
}
function EmptyState({ icon, title, description, }) {
    return (_jsxs("div", { role: "status", className: "flex flex-col items-center gap-2 py-8 text-center", children: [icon && _jsx("div", { className: "text-muted-foreground-faint", children: icon }), _jsx("p", { className: "text-sm font-medium text-muted-foreground", children: title }), _jsx("p", { className: "text-xs text-muted-foreground-subtle", children: description })] }));
}
function ErrorState({ message, onRetry, }) {
    return (_jsxs("div", { role: "alert", className: "flex flex-col items-center gap-2 py-8 text-center", children: [_jsx("p", { className: "text-sm text-destructive", children: message }), onRetry && (_jsx("button", { type: "button", onClick: onRetry, className: cn("inline-flex items-center rounded-md px-3 py-1.5 text-xs font-medium", "border border-input bg-background text-foreground", "hover:bg-accent hover:text-accent-foreground", "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"), children: "Retry" }))] }));
}
function PaginationBar({ currentPage, totalPages, totalCount, onPageChange, }) {
    const navButtonClass = cn("inline-flex items-center rounded-md p-1.5", "border border-input bg-background text-foreground", "hover:bg-accent hover:text-accent-foreground", "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", "disabled:pointer-events-none disabled:opacity-50");
    return (_jsxs("nav", { "aria-label": "Pagination", className: "flex items-center justify-between border-t border-border pt-3", children: [_jsx("span", { className: "text-xs text-muted-foreground", children: totalCount !== undefined &&
                    `${totalCount.toLocaleString()} ${totalCount === 1 ? "result" : "results"}` }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("button", { type: "button", onClick: () => onPageChange(currentPage - 1), disabled: currentPage <= 1, "aria-label": "Previous page", className: navButtonClass, children: _jsx(ChevronLeftIcon, { className: "h-3.5 w-3.5" }) }), _jsxs("span", { className: "text-xs text-muted-foreground", children: ["Page ", currentPage, " of ", totalPages] }), _jsx("button", { type: "button", onClick: () => onPageChange(currentPage + 1), disabled: currentPage >= totalPages, "aria-label": "Next page", className: navButtonClass, children: _jsx(ChevronRightIcon, { className: "h-3.5 w-3.5" }) })] })] }));
}
// ---------------------------------------------------------------------------
// Icons — inline SVGs following the existing SDK pattern
// ---------------------------------------------------------------------------
function SearchIcon({ className }) {
    return (_jsxs("svg", { className: className, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [_jsx("circle", { cx: "6.5", cy: "6.5", r: "4.5" }), _jsx("path", { d: "m10 10 4 4" })] }));
}
function ChevronLeftIcon({ className }) {
    return (_jsx("svg", { className: className, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: _jsx("path", { d: "m10 3-5 5 5 5" }) }));
}
function ChevronRightIcon({ className }) {
    return (_jsx("svg", { className: className, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: _jsx("path", { d: "m6 3 5 5-5 5" }) }));
}
function AgentIcon({ className }) {
    return (_jsxs("svg", { className: className, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [_jsx("rect", { x: "3", y: "5", width: "10", height: "8", rx: "1.5" }), _jsx("path", { d: "M6 9h.01M10 9h.01", strokeWidth: "2" }), _jsx("path", { d: "M8 2v3" })] }));
}
function SkillIcon({ className }) {
    return (_jsx("svg", { className: className, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: _jsx("path", { d: "M9 1.5 4 9h4l-1 5.5L12 7H8l1-5.5Z" }) }));
}
function McpServerIcon({ className }) {
    return (_jsxs("svg", { className: className, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [_jsx("rect", { x: "2", y: "2", width: "12", height: "5", rx: "1" }), _jsx("rect", { x: "2", y: "9", width: "12", height: "5", rx: "1" }), _jsx("circle", { cx: "5", cy: "4.5", r: "0.75", fill: "currentColor", stroke: "none" }), _jsx("circle", { cx: "5", cy: "11.5", r: "0.75", fill: "currentColor", stroke: "none" })] }));
}
function WorkflowIcon({ className }) {
    return (_jsxs("svg", { className: className, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [_jsx("circle", { cx: "4", cy: "4", r: "2" }), _jsx("circle", { cx: "12", cy: "4", r: "2" }), _jsx("circle", { cx: "8", cy: "12", r: "2" }), _jsx("path", { d: "M4 6v2a2 2 0 0 0 2 2h4a2 2 0 0 0 2-2V6" })] }));
}
function DocumentIcon({ className }) {
    return (_jsxs("svg", { className: className, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [_jsx("path", { d: "M9 1.5H5c-.6 0-1 .4-1 1v11c0 .6.4 1 1 1h6c.6 0 1-.4 1-1V5Z" }), _jsx("path", { d: "M9 1.5V5h3" })] }));
}
//# sourceMappingURL=ResourceListView.js.map