'use client';
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import { useCallback, useEffect, useMemo, useRef, useState, useTransition } from 'react';
import { useTranslation } from 'react-i18next';
import { ReactFlow, ReactFlowProvider, Background, Controls, Position, addEdge, useEdgesState, useNodesState, } from '@xyflow/react';
import '@xyflow/react/dist/style.css';
import { Pencil, Plus, RotateCcw, Save, Trash2, X } from 'lucide-react';
import { Alert, AlertDescription } from '../ui/alert.js';
import { Badge } from '../ui/badge.js';
import { Button } from '../ui/button.js';
import { layoutWithDagre } from '../../lib/layout-with-dagre.js';
import { resetAgentGraph, saveAgentGraph } from '../../app/actions/agent-graph.js';
const NODE_TYPES = { editable: EditableNode };
export function AgentGraphView(props) {
    const { t } = useTranslation('web');
    if (!props.graph) {
        return (_jsx("p", { className: "text-muted-foreground rounded-lg border border-dashed p-4 text-sm", "data-testid": "agent-graph-empty", children: t('agentEditor.noGraphDescriptor') }));
    }
    return (_jsx(ReactFlowProvider, { children: _jsx(AgentGraphInner, { ...props, graph: props.graph }) }));
}
function AgentGraphInner({ graph, bundled, hasOverride, onSaveOverride, onResetOverride, initialEditing, }) {
    const { t } = useTranslation('web');
    const [editing, setEditing] = useState(Boolean(initialEditing));
    const [error, setError] = useState(null);
    const [savedAt, setSavedAt] = useState(null);
    const [isPending, startTransition] = useTransition();
    const idCounterRef = useRef(0);
    const initial = useMemo(() => buildFlow(graph), [graph]);
    const [nodes, setNodes, onNodesChange] = useNodesState(initial.nodes);
    const [edges, setEdges, onEdgesChange] = useEdgesState(initial.edges);
    // Re-sync when server data refreshes (revalidatePath after save).
    useEffect(() => {
        setNodes(initial.nodes);
        setEdges(initial.edges);
    }, [initial.nodes, initial.edges, setNodes, setEdges]);
    const handleLabelChange = useCallback((id, value) => {
        setNodes((curr) => curr.map((n) => (n.id === id ? { ...n, data: { ...n.data, label: value } } : n)));
    }, [setNodes]);
    const handleDescriptionChange = useCallback((id, value) => {
        setNodes((curr) => curr.map((n) => (n.id === id ? { ...n, data: { ...n.data, description: value } } : n)));
    }, [setNodes]);
    const handleDelete = useCallback((id) => {
        setNodes((curr) => curr.filter((n) => n.id !== id));
        setEdges((curr) => curr.filter((e) => e.source !== id && e.target !== id));
    }, [setNodes, setEdges]);
    // Inject latest callbacks + editing flag into node.data (without re-laying-out).
    const renderedNodes = useMemo(() => nodes.map((n) => ({
        ...n,
        type: 'editable',
        data: {
            ...n.data,
            editing,
            onLabelChange: handleLabelChange,
            onDescriptionChange: handleDescriptionChange,
            onDelete: handleDelete,
        },
    })), [nodes, editing, handleLabelChange, handleDescriptionChange, handleDelete]);
    function handleConnect(params) {
        setEdges((curr) => addEdge({ ...params, type: 'smoothstep' }, curr));
    }
    function handleAddNode() {
        idCounterRef.current += 1;
        const id = `node-${Date.now()}-${idCounterRef.current}`;
        setNodes((curr) => [
            ...curr,
            {
                id,
                type: 'editable',
                position: nextPosition(curr),
                data: {
                    label: t('agentEditor.newStep'),
                    description: '',
                    editing: true,
                    onLabelChange: handleLabelChange,
                    onDescriptionChange: handleDescriptionChange,
                    onDelete: handleDelete,
                },
                sourcePosition: Position.Right,
                targetPosition: Position.Left,
            },
        ]);
    }
    function handleSave() {
        setError(null);
        const payload = serialize(nodes, edges);
        const validation = validate(payload, t);
        if (validation) {
            setError(validation);
            return;
        }
        startTransition(async () => {
            const fn = onSaveOverride ?? saveAgentGraph;
            const result = await fn({ agentType: graph.agentType, ...payload });
            if (!result.ok) {
                setError(result.error ?? t('agentEditor.failedToSaveGraph'));
                return;
            }
            setSavedAt(new Date());
            setEditing(false);
        });
    }
    function handleCancel() {
        setNodes(initial.nodes);
        setEdges(initial.edges);
        setError(null);
        setEditing(false);
    }
    function handleReset() {
        setError(null);
        startTransition(async () => {
            const fn = onResetOverride ?? resetAgentGraph;
            const result = await fn({ agentType: graph.agentType });
            if (!result.ok) {
                setError(result.error ?? t('agentEditor.failedToResetGraph'));
                return;
            }
            setSavedAt(new Date());
            setEditing(false);
            // Re-sync from bundled when caller supplied it; otherwise the
            // revalidatePath in the server action will swap initial via props.
            if (bundled) {
                const fresh = buildFlow(bundled);
                setNodes(fresh.nodes);
                setEdges(fresh.edges);
            }
        });
    }
    return (_jsxs("div", { className: "flex flex-col gap-3", "data-testid": "agent-graph-view", children: [_jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("p", { className: "text-sm font-medium", children: graph.agentType }), hasOverride ? (_jsx(Badge, { variant: "default", "data-testid": "graph-badge-overridden", children: t('agentEditor.overridden') })) : (_jsx(Badge, { variant: "secondary", children: t('agentEditor.bundled') }))] }), _jsx("div", { className: "flex items-center gap-2", children: editing ? (_jsxs(_Fragment, { children: [_jsxs(Button, { type: "button", variant: "ghost", size: "sm", onClick: handleAddNode, disabled: isPending, "data-testid": "graph-add-node", children: [_jsx(Plus, { className: "size-3" }), t('agentEditor.addNode')] }), hasOverride ? (_jsxs(Button, { type: "button", variant: "ghost", size: "sm", onClick: handleReset, disabled: isPending, "data-testid": "graph-reset", children: [_jsx(RotateCcw, { className: "size-3" }), t('agentEditor.resetToBundled')] })) : null, _jsxs(Button, { type: "button", variant: "ghost", size: "sm", onClick: handleCancel, disabled: isPending, "data-testid": "graph-cancel", children: [_jsx(X, { className: "size-3" }), t('agentEditor.cancel')] }), _jsxs(Button, { type: "button", size: "sm", onClick: handleSave, disabled: isPending, "data-testid": "graph-save", children: [_jsx(Save, { className: "size-3" }), isPending ? t('agentEditor.saving') : t('agentEditor.saveGraph')] })] })) : (_jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: () => setEditing(true), "data-testid": "graph-edit", children: [_jsx(Pencil, { className: "size-3" }), t('agentEditor.edit')] })) })] }), error ? (_jsx(Alert, { variant: "destructive", "data-testid": "graph-error", children: _jsx(AlertDescription, { children: error }) })) : null, savedAt && !editing ? (_jsx("p", { className: "text-muted-foreground text-xs", "data-testid": "graph-saved", children: t('agentEditor.savedAt', { time: savedAt.toLocaleTimeString() }) })) : null, _jsx("div", { className: "bg-muted/20 h-[480px] w-full overflow-hidden rounded-lg border", children: _jsxs(ReactFlow, { nodes: renderedNodes, edges: edges, nodeTypes: NODE_TYPES, onNodesChange: onNodesChange, onEdgesChange: onEdgesChange, onConnect: handleConnect, nodesDraggable: editing, nodesConnectable: editing, elementsSelectable: editing, deleteKeyCode: editing ? ['Backspace', 'Delete'] : null, fitView: true, proOptions: { hideAttribution: true }, children: [_jsx(Background, { gap: 20 }), _jsx(Controls, { showInteractive: false })] }) })] }));
}
// ── React Flow node ────────────────────────────────────────────────────────────
import { Handle } from '@xyflow/react';
function EditableNode({ id, data }) {
    const { t } = useTranslation('web');
    const { label, description, editing, onLabelChange, onDescriptionChange, onDelete } = data;
    return (_jsxs("div", { className: "bg-card relative min-w-[180px] rounded-md border px-2 py-1.5 shadow-sm", children: [_jsx(Handle, { type: "target", position: Position.Left }), editing ? (_jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("input", { value: label, onChange: (e) => onLabelChange(id, e.target.value), className: "border-border bg-background w-full rounded border px-1 py-0.5 text-xs font-semibold", placeholder: t('agentEditor.nodeLabelPlaceholder'), "aria-label": t('agentEditor.nodeLabelAria', { id }), "data-testid": `graph-node-label-${id}` }), _jsx("input", { value: description ?? '', onChange: (e) => onDescriptionChange(id, e.target.value), className: "border-border bg-background w-full rounded border px-1 py-0.5 text-[10px]", placeholder: t('agentEditor.nodeDescriptionPlaceholder'), "aria-label": t('agentEditor.nodeDescriptionAria', { id }), "data-testid": `graph-node-description-${id}` }), _jsx("button", { type: "button", onClick: () => onDelete(id), className: "text-destructive absolute -top-2 -right-2 flex size-4 items-center justify-center rounded-full border bg-white shadow-sm", "aria-label": t('agentEditor.deleteNodeAria', { id }), "data-testid": `graph-node-delete-${id}`, children: _jsx(Trash2, { className: "size-2.5" }) })] })) : (_jsxs("div", { className: "flex flex-col items-start gap-0.5 text-left", children: [_jsx("span", { className: "text-xs font-semibold", children: label }), description ? (_jsx("span", { className: "text-muted-foreground text-[10px]", children: description })) : null] })), _jsx(Handle, { type: "source", position: Position.Right })] }));
}
// ── helpers ────────────────────────────────────────────────────────────────────
function buildFlow(graph) {
    const nodes = graph.nodes.map((n) => ({
        id: n.id,
        type: 'editable',
        position: { x: 0, y: 0 },
        data: {
            label: n.label,
            description: n.description ?? '',
            editing: false,
            onLabelChange: () => undefined,
            onDescriptionChange: () => undefined,
            onDelete: () => undefined,
        },
        sourcePosition: Position.Right,
        targetPosition: Position.Left,
    }));
    const edges = graph.edges.map((e, idx) => ({
        id: `e-${idx}-${e.from}-${e.to}`,
        source: e.from,
        target: e.to,
        type: 'smoothstep',
        label: e.label,
        animated: false,
    }));
    const laidOut = layoutWithDagre(nodes, edges, {
        direction: 'LR',
        nodeSize: { width: 220, height: 70 },
        nodesep: 30,
        ranksep: 80,
    });
    return { nodes: laidOut.nodes, edges: laidOut.edges };
}
function nextPosition(existing) {
    if (existing.length === 0)
        return { x: 0, y: 0 };
    const maxX = existing.reduce((m, n) => Math.max(m, n.position.x), 0);
    const minY = existing.reduce((m, n) => Math.min(m, n.position.y), 0);
    return { x: maxX + 240, y: minY };
}
function serialize(nodes, edges) {
    return {
        nodes: nodes.map((n) => ({
            id: n.id,
            label: n.data.label.trim() || n.id,
            ...(n.data.description?.trim() ? { description: n.data.description.trim() } : {}),
        })),
        edges: edges.map((e) => ({
            from: e.source,
            to: e.target,
            ...(typeof e.label === 'string' && e.label.trim() ? { label: e.label.trim() } : {}),
        })),
    };
}
function validate(payload, t) {
    if (payload.nodes.length === 0)
        return t('agentEditor.graphMustHaveNode');
    const ids = new Set();
    for (const n of payload.nodes) {
        if (!n.label)
            return t('agentEditor.nodeMustHaveLabel', { id: n.id });
        if (ids.has(n.id))
            return t('agentEditor.duplicateNodeId', { id: n.id });
        ids.add(n.id);
    }
    for (const e of payload.edges) {
        if (!ids.has(e.from))
            return t('agentEditor.edgeUnknownSource', { id: e.from });
        if (!ids.has(e.to))
            return t('agentEditor.edgeUnknownTarget', { id: e.to });
    }
    return null;
}
