'use client';

import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useRouter } from 'next/navigation';
import { useTranslation } from 'react-i18next';
import {
  ReactFlowProvider,
  useNodesState,
  useEdgesState,
  addEdge,
  type Connection,
  type Edge,
  type Node,
} from '@xyflow/react';
import { Box, Flex, Text, Button, Dialog, Callout, VisuallyHidden } from '@radix-ui/themes';
import { AgentsApi } from '../api';
import { extractAgentConfigFromFlow } from './extract-agent-config';
import { useAgentBuilderData } from './hooks/use-agent-builder-data';
import { useAgentBuilderState } from './hooks/use-agent-builder-state';
import { useAgentBuilderNodeTemplates } from './hooks/use-node-templates';
import { useAgentBuilderReconstruction } from './hooks/use-flow-reconstruction';
import { AgentBuilderHeader } from './components/agent-builder-header';
import { AgentBuilderSidebar } from './components/agent-builder-sidebar';
import { AgentBuilderCanvas } from './components/agent-builder-canvas';
import { DeleteAgentDialog } from '@/app/(main)/chat/sidebar/dialogs';
import { ServiceAccountConfirmDialog } from './components/service-account-confirm-dialog';
import { AgentToolsetCredentialsDialog } from './components/agent-toolset-credentials-dialog';
import type { BuilderSidebarToolset } from '@/app/(main)/toolsets/api';
import type { AgentWebSearchAttachment, FlowNodeData } from './types';
import type { WebSearchProviderType } from '../../workspace/web-search/types';
import { normalizeDisplayName, formattedProvider } from './display-utils';
import { FLOW_EDGE } from './flow-theme';
import { connectionError } from './connection-rules';
import { buildChatHref } from '@/chat/build-chat-url';
import { invalidateModelsForContext } from '@/chat/utils/fetch-models-for-context';
import { MaterialIcon } from '@/app/components/ui/MaterialIcon';
import { getAgentBuilderPermissions } from './agent-builder-permissions';
import { toast } from '@/lib/store/toast-store';
import {
  collectActiveToolsetTypeKeysFromNodes,
  type ToolsetTypeKeyFlowNode,
} from './sidebar-toolset-utils';

/** Palette width: comfortable for labels; chrome matches `SecondaryPanel` / chat sidebars. */
const AGENT_BUILDER_SIDEBAR_WIDTH = 332;

const SVC_ACCT_TOOLSET_BLOCK_TOAST_MS = 9000;

/** Extract a human-readable message from an unknown API error. */
function extractErrorMessage(e: unknown, fallback: string): string {
  const detail = (e as { response?: { data?: { detail?: string } } })?.response?.data?.detail;
  if (typeof detail === 'string' && detail.trim()) return detail.trim();
  if (e instanceof Error && e.message) return e.message;
  return fallback;
}

// ── Dirty-tracking helpers ───────────────────────────────────────────────────
type CleanSnapshot = {
  nodesJson: string;
  edgesJson: string;
  agentName: string;
  shareWithOrg: boolean;
};

function serializeNodes(nodes: Node[]): string {
  return JSON.stringify(
    [...nodes]
      .sort((a, b) => a.id.localeCompare(b.id))
      .map(({ id, type, data }) => ({ id, type, data }))
  );
}

function serializeEdges(edges: Edge[]): string {
  return JSON.stringify(
    [...edges]
      .sort((a, b) => a.id.localeCompare(b.id))
      .map(({ id, source, target, sourceHandle, targetHandle }) => ({
        id, source, target, sourceHandle, targetHandle,
      }))
  );
}

export function AgentBuilder({ agentKey }: { agentKey: string | null }) {
  const router = useRouter();
  const { t } = useTranslation();
  const editingKey = agentKey;

  const {
    availableTools,
    availableModels,
    availableKnowledgeBases,
    activeAgentConnectors,
    configuredConnectors,
    toolsets,
    loading,
    loadedAgent,
    error,
    setError,
    refreshToolsets,
    refreshAgent,
  } = useAgentBuilderData(editingKey);

  const {
    deleteDialogOpen,
    setDeleteDialogOpen,
    nodeToDelete,
    setNodeToDelete,
    edgeDeleteDialogOpen,
    setEdgeDeleteDialogOpen,
    edgeToDelete,
    setEdgeToDelete,
    sidebarOpen,
    setSidebarOpen,
    agentName,
    setAgentName,
    saving,
    setSaving,
    deleting,
    setDeleting,
    success,
    setSuccess,
  } = useAgentBuilderState(loadedAgent?.name || '');

  const [shareWithOrg, setShareWithOrg] = useState(false);
  const [banner, setBanner] = useState<string | null>(null);
  const [showPostUpdateDialog, setShowPostUpdateDialog] = useState(false);

  // ── Dirty tracking ──────────────────────────────────────────────────────────
  // A snapshot of the last-saved (or initially loaded) state. isDirty is derived
  // by comparing current state against it, so add-then-revert → clean again.
  const [cleanSnapshot, setCleanSnapshot] = useState<CleanSnapshot | null>(null);

  const [serviceAccountConfirmOpen, setServiceAccountConfirmOpen] = useState(false);
  const [serviceAccountCreating, setServiceAccountCreating] = useState(false);
  const [serviceAccountError, setServiceAccountError] = useState<string | null>(null);
  const [agentToolsetDialog, setAgentToolsetDialog] = useState<{
    toolset: BuilderSidebarToolset;
    instanceId: string;
  } | null>(null);
  const [agentDeleteDialogOpen, setAgentDeleteDialogOpen] = useState(false);
  const [isDeletingAgent, setIsDeletingAgent] = useState(false);
  const [agentNameError, setAgentNameError] = useState<string | null>(null);
  const agentNameInputRef = useRef<HTMLInputElement>(null);

  const effectiveAgentKey = loadedAgent?._key ?? editingKey ?? null;

  const { nodeTemplates } = useAgentBuilderNodeTemplates(
    availableTools,
    availableModels,
    availableKnowledgeBases,
    configuredConnectors
  );
  const { reconstructFlowFromAgent } = useAgentBuilderReconstruction();

  const [nodes, setNodes, onNodesChange] = useNodesState<Node<FlowNodeData>>([]);
  const [edges, setEdges, onEdgesChange] = useEdgesState<Edge>([]);

  const { canPersist, isAgentStructureLocked, isServiceAccountToolsetOrgLocked, isServiceAccount } =
    useMemo(() => getAgentBuilderPermissions(loadedAgent), [loadedAgent]);

  const paletteDragBlockedMessage = useMemo(() => {
    if (!isAgentStructureLocked) return '';
    return isServiceAccountToolsetOrgLocked
      ? t('agentBuilder.paletteActionBlockedViewOnly')
      : t('agentBuilder.viewerPaletteDragBlocked');
  }, [isAgentStructureLocked, isServiceAccountToolsetOrgLocked, t]);

  useEffect(() => {
    if (loadedAgent) {
      setShareWithOrg(isServiceAccount ? true : Boolean(loadedAgent.shareWithOrg));
    }
  }, [loadedAgent, isServiceAccount]);

  useEffect(() => {
    if (loadedAgent?.name) setAgentName(loadedAgent.name);
  }, [loadedAgent?.name, setAgentName]);

  useEffect(() => {
    if (agentName.trim()) setAgentNameError(null);
  }, [agentName]);

  // Auto-dismiss connection error banner after 3.5 s
  useEffect(() => {
    if (!banner) return;
    const t = setTimeout(() => setBanner(null), 3500);
    return () => clearTimeout(t);
  }, [banner]);

  // Auto-dismiss success toast after 4 s
  useEffect(() => {
    if (!success) return;
    const t = setTimeout(() => setSuccess(null), 4000);
    return () => clearTimeout(t);
  }, [success, setSuccess]);

  const prevAgentKeyRef = useRef<string | null>(null);
  useEffect(() => {
    const prev = prevAgentKeyRef.current;
    if (editingKey && prev && prev !== editingKey && !loading) {
      setNodes([]);
      setEdges([]);
    }
    prevAgentKeyRef.current = editingKey;
  }, [editingKey, loading, setNodes, setEdges]);

  const initOnce = useRef(false);
  useEffect(() => {
    initOnce.current = false;
    historyGuardActive.current = false;
    setCleanSnapshot(null);
  }, [editingKey]);

  useEffect(() => {
    if (loading || availableModels.length === 0 || nodes.length > 0 || initOnce.current) return;

    const agentSrc = loadedAgent || undefined;
    if (agentSrc) {
      const { nodes: n, edges: e } = reconstructFlowFromAgent(
        { ...agentSrc, knowledge: (agentSrc.knowledge as unknown[]) || [] },
        availableModels,
        availableTools,
        availableKnowledgeBases
      );
      setNodes(n);
      setEdges(e);
      setCleanSnapshot({
        nodesJson: serializeNodes(n),
        edgesJson: serializeEdges(e),
        agentName: agentSrc.name || '',
        shareWithOrg: Boolean(agentSrc.shareWithOrg),
      });
      initOnce.current = true;
      return;
    }

    const initialModel =
      availableModels.find((m) => m.isReasoning) || availableModels[0];
    if (!initialModel) return;

    const systemPrompt = t('agentBuilder.defaultSystemPrompt');
    const startMessage = t('agentBuilder.defaultStartMessage');

    const initialNodes: Node<FlowNodeData>[] = [
      {
        id: 'chat-input-1',
        type: 'flowNode',
        position: { x: 50, y: 520 },
        data: {
          id: 'chat-input-1',
          type: 'user-input',
          label: t('agentBuilder.nodeLabelChatInput'),
          description: t('agentBuilder.nodeDescUserMessages'),
          icon: 'chat',
          config: { placeholder: t('agentBuilder.chatInputPlaceholder') },
          inputs: [],
          outputs: ['message'],
          isConfigured: true,
        },
      },
      {
        id: 'llm-1',
        type: 'flowNode',
        position: { x: 50, y: 220 },
        data: {
          id: 'llm-1',
          type: `llm-${(initialModel.modelKey || 'default').replace(/[^a-zA-Z0-9]/g, '-').toLowerCase()}`,
          label: initialModel.modelFriendlyName?.trim() || initialModel.modelName || 'Model',
          description: `${formattedProvider(initialModel.provider || 'AI')} model`,
          icon: 'psychology',
          config: {
            modelKey: initialModel.modelKey,
            modelName: initialModel.modelName,
            provider: initialModel.provider || 'azureOpenAI',
            modelType: initialModel.modelType || 'llm',
            isMultimodal: initialModel.isMultimodal,
            isDefault: initialModel.isDefault,
            isReasoning: initialModel.isReasoning,
            modelFriendlyName: initialModel.modelFriendlyName,
          },
          inputs: [],
          outputs: ['response'],
          isConfigured: true,
        },
      },
      {
        id: 'agent-core-1',
        type: 'flowNode',
        position: { x: 420, y: 120 },
        data: {
          id: 'agent-core-1',
          type: 'agent-core',
          label: normalizeDisplayName(t('agentBuilder.coreNodeTitle')),
          description: t('agentBuilder.coreNodeSubtitle'),
          icon: 'auto_awesome',
          config: {
            systemPrompt,
            instructions: '',
            startMessage,
            routing: 'auto',
            allowMultipleLLMs: true,
          },
          inputs: ['input', 'toolsets', 'knowledge', 'llms'],
          outputs: ['response'],
          isConfigured: true,
        },
      },
      {
        id: 'chat-response-1',
        type: 'flowNode',
        position: { x: 820, y: 320 },
        data: {
          id: 'chat-response-1',
          type: 'chat-response',
          label: t('agentBuilder.nodeLabelChatOutput'),
          description: t('agentBuilder.nodeDescChatReply'),
          icon: 'reply',
          config: { format: 'text' },
          inputs: ['response'],
          outputs: [],
          isConfigured: true,
        },
      },
    ];

    const initialEdges: Edge[] = [
      {
        id: 'e-input-agent',
        source: 'chat-input-1',
        target: 'agent-core-1',
        sourceHandle: 'message',
        targetHandle: 'input',
        type: 'smoothstep',
        style: { stroke: FLOW_EDGE.line, strokeWidth: 1.5 },
      },
      {
        id: 'e-llm-agent',
        source: 'llm-1',
        target: 'agent-core-1',
        sourceHandle: 'response',
        targetHandle: 'llms',
        type: 'smoothstep',
        style: { stroke: FLOW_EDGE.line, strokeWidth: 1.5 },
      },
      {
        id: 'e-agent-output',
        source: 'agent-core-1',
        target: 'chat-response-1',
        sourceHandle: 'response',
        targetHandle: 'response',
        type: 'smoothstep',
        style: { stroke: FLOW_EDGE.line, strokeWidth: 1.5 },
      },
    ];

    setNodes(initialNodes);
    setEdges(initialEdges);
    initOnce.current = true;
  }, [
    loading,
    availableModels,
    availableKnowledgeBases,
    availableTools,
    loadedAgent,
    nodes.length,
    reconstructFlowFromAgent,
    setEdges,
    setNodes,
    t,
  ]);

  const onConnect = useCallback(
    (connection: Connection) => {
      if (isAgentStructureLocked) return;
      const sourceNode = nodes.find((n) => n.id === connection.source);
      const targetNode = nodes.find((n) => n.id === connection.target);
      const msgKey = connectionError(sourceNode, targetNode, connection);
      if (msgKey) {
        setBanner(t(msgKey));
        return;
      }
      setEdges((eds) =>
        addEdge(
          {
            ...connection,
            id: `e-${connection.source}-${connection.target}-${Date.now()}`,
            type: 'smoothstep',
            style: { stroke: FLOW_EDGE.line, strokeWidth: 1.5 },
          },
          eds
        )
      );
    },
    [isAgentStructureLocked, nodes, setEdges, t]
  );

  const onEdgeClick = useCallback(
    (_: React.MouseEvent, edge: Edge) => {
      if (isAgentStructureLocked) return;
      setEdgeToDelete(edge.id);
      setEdgeDeleteDialogOpen(true);
    },
    [isAgentStructureLocked, setEdgeDeleteDialogOpen, setEdgeToDelete]
  );

  const hasToolsets = useMemo(
    () => nodes.some((n) => String(n.data?.type ?? '').startsWith('toolset-')),
    [nodes]
  );

  const saWelcomeShownRef = useRef(false);
  useEffect(() => {
    if (saWelcomeShownRef.current || typeof window === 'undefined' || !editingKey) return;
    const q = new URLSearchParams(window.location.search);
    if (q.get('sa') !== '1') return;
    saWelcomeShownRef.current = true;
    setSuccess(t('agentBuilder.serviceAccountCreated'));
    q.delete('sa');
    const path = window.location.pathname;
    const rest = q.toString();
    void router.replace(rest ? `${path}?${rest}` : path);
  }, [editingKey, router, setSuccess]);

  // ── Dirty detection (snapshot comparison) ───────────────────────────────────
  // Compares current state against the clean snapshot on every relevant change.
  // Reverts to false automatically when the user undoes all edits.
  const isDirty = useMemo(() => {
    if (!cleanSnapshot) return false;
    return (
      serializeNodes(nodes) !== cleanSnapshot.nodesJson ||
      serializeEdges(edges) !== cleanSnapshot.edgesJson ||
      agentName.trim() !== cleanSnapshot.agentName ||
      shareWithOrg !== cleanSnapshot.shareWithOrg
    );
  }, [nodes, edges, agentName, shareWithOrg, cleanSnapshot]);

  // beforeunload guard: browser tab close / refresh / address-bar navigation.
  useEffect(() => {
    if (!isDirty) return;
    const handler = (e: BeforeUnloadEvent) => {
      e.preventDefault();
    };
    window.addEventListener('beforeunload', handler);
    return () => window.removeEventListener('beforeunload', handler);
  }, [isDirty]);

  // popstate guard: browser back/forward button.
  // When dirty, push a guard entry so the first back press hits it (same URL)
  // and fires popstate without actually leaving the page.
  const historyGuardActive = useRef(false);
  useEffect(() => {
    if (!isDirty) return;

    if (!historyGuardActive.current) {
      window.history.pushState(null, '');
      historyGuardActive.current = true;
    }

    const handler = () => {
      // Guard against re-entry: the history.back() below queues another
      // popstate that would fire before Next.js unmounts the page.
      if (!historyGuardActive.current) return;
      const confirmed = window.confirm(t('agentBuilder.unsavedChangesConfirm'));
      if (confirmed) {
        // User chose to leave: clear flag and go back past the guard entry.
        historyGuardActive.current = false;
        window.history.back();
      } else {
        // User chose to stay: re-push the guard so the next back press is caught too.
        window.history.pushState(null, '');
      }
    };

    window.addEventListener('popstate', handler);
    return () => window.removeEventListener('popstate', handler);
  }, [isDirty, t]);

  // Clear the back-stack sentinel when the page becomes clean
  // (e.g., after a successful save or full undo). Without this,
  // the pushed history entry leaks and the user has to press Back twice.
  const prevDirtyRef = useRef(false);
  useEffect(() => {
    if (prevDirtyRef.current && !isDirty && historyGuardActive.current) {
      historyGuardActive.current = false;
      window.history.back();
    }
    prevDirtyRef.current = isDirty;
  }, [isDirty]);

  /** Logical toolset types already on the canvas (legacy: at most one per type). */
  const activeToolsetTypeKeys = useMemo(() => collectActiveToolsetTypeKeysFromNodes(nodes), [nodes]);

  const webSearchNode = useMemo(
    () => nodes.find((n) => n.data?.type === 'web-search') ?? null,
    [nodes],
  );

  const webSearchAttached = useMemo<AgentWebSearchAttachment | null>(() => {
    if (!webSearchNode) return null;
    const cfg = webSearchNode.data.config || {};
    const provider = cfg.provider as string | undefined;
    if (!provider) return null;
    return {
      provider: provider as WebSearchProviderType,
      providerKey: (cfg.providerKey as string) || '',
      providerLabel: (cfg.providerLabel as string) || undefined,
      iconPath: (cfg.iconPath as string) || undefined,
    };
  }, [webSearchNode]);

  const saveRef = useRef(false);

  const focusAgentNameInput = useCallback(() => {
    queueMicrotask(() => {
      agentNameInputRef.current?.focus();
    });
  }, []);

  /** Inline error + focus — shared by save, service-account entry, and confirm edge cases. */
  const showInlineAgentNameRequired = useCallback(() => {
    setAgentNameError(t('agentBuilder.nameRequired'));
    focusAgentNameInput();
  }, [focusAgentNameInput, t]);

  const notifyServiceAccountToolsetBlocked = useCallback(() => {
    const converting = Boolean(loadedAgent);
    toast.error(t('agentBuilder.svcAcctRemoveToolsetsTitle'), {
      description: t('agentBuilder.svcAcctRemoveToolsetsDesc', {
        action: converting ? t('agentBuilder.svcAcctConvertAction') : t('agentBuilder.svcAcctCreateAction'),
      }),
      duration: SVC_ACCT_TOOLSET_BLOCK_TOAST_MS,
    });
  }, [loadedAgent, t]);

  const handleRequestServiceAccount = useCallback(() => {
    if (!agentName.trim()) {
      toast.error(t('agentBuilder.nameRequired'), {
        description: t('agentBuilder.svcAcctNameRequired'),
      });
      showInlineAgentNameRequired();
      return;
    }
    if (hasToolsets) {
      notifyServiceAccountToolsetBlocked();
      return;
    }
    setServiceAccountConfirmOpen(true);
  }, [agentName, hasToolsets, notifyServiceAccountToolsetBlocked, showInlineAgentNameRequired, t]);

  const handleSave = useCallback(async () => {
    if (!canPersist) return;
    if (saveRef.current) return;
    if (!agentName.trim()) {
      showInlineAgentNameRequired();
      setBanner(null);
      return;
    }
    saveRef.current = true;
    setSaving(true);
    setError(null);
    setBanner(null);
    try {
      const payload = {
        ...extractAgentConfigFromFlow(
          agentName.trim(),
          nodes as Parameters<typeof extractAgentConfigFromFlow>[1],
          edges as Parameters<typeof extractAgentConfigFromFlow>[2],
          loadedAgent,
          shareWithOrg,
          isServiceAccount
        ),
        // Persist the visual layout so positions survive subsequent edits.
        flow: { nodes, edges },
      };

      if (loadedAgent) {
        const updated = await AgentsApi.updateAgent(loadedAgent._key, payload);
        // Agent's model list may have changed — drop the chat-side cache so
        // the next chat view for this agent refetches fresh models.
        invalidateModelsForContext(loadedAgent._key);
        await refreshAgent(loadedAgent._key, { knownAgent: updated });
        setCleanSnapshot({
          nodesJson: serializeNodes(nodes),
          edgesJson: serializeEdges(edges),
          agentName: agentName.trim(),
          shareWithOrg,
        });
        setShowPostUpdateDialog(true);
      } else {
        const created = await AgentsApi.createAgent(payload);
        invalidateModelsForContext(created._key);
        setCleanSnapshot({
          nodesJson: serializeNodes(nodes),
          edgesJson: serializeEdges(edges),
          agentName: agentName.trim(),
          shareWithOrg,
        });
        setSuccess(t('agentBuilder.agentCreated'));
        router.replace(`/agents/edit?agentKey=${encodeURIComponent(created._key)}`);
      }
    } catch (e: unknown) {
      setError(extractErrorMessage(e, t('agentBuilder.saveFailed')));
    } finally {
      setSaving(false);
      saveRef.current = false;
    }
  }, [
    agentName,
    edges,
    canPersist,
    showInlineAgentNameRequired,
    isServiceAccount,
    loadedAgent,
    nodes,
    refreshAgent,
    router,
    setError,
    setSaving,
    setSuccess,
    shareWithOrg,
    t,
  ]);

  const handleConfirmServiceAccount = useCallback(async () => {
    if (loadedAgent && !canPersist) return;
    if (!agentName.trim()) {
      setServiceAccountConfirmOpen(false);
      setServiceAccountError(null);
      toast.error(t('agentBuilder.nameRequired'), {
        description: t('agentBuilder.svcAcctNameRequired'),
      });
      showInlineAgentNameRequired();
      return;
    }
    if (hasToolsets) {
      setServiceAccountConfirmOpen(false);
      setServiceAccountError(null);
      notifyServiceAccountToolsetBlocked();
      return;
    }
    setServiceAccountCreating(true);
    setServiceAccountError(null);
    try {
      const currentAgent = loadedAgent;
      const agentConfig = {
        ...extractAgentConfigFromFlow(
          agentName.trim(),
          nodes as Parameters<typeof extractAgentConfigFromFlow>[1],
          edges as Parameters<typeof extractAgentConfigFromFlow>[2],
          currentAgent ?? null,
          true,
          true
        ),
        flow: { nodes, edges },
      };

      if (currentAgent) {
        const updated = await AgentsApi.updateAgent(currentAgent._key, agentConfig);
        invalidateModelsForContext(currentAgent._key);
        setServiceAccountConfirmOpen(false);
        await refreshAgent(currentAgent._key, { knownAgent: updated });
        setCleanSnapshot({
          nodesJson: serializeNodes(nodes),
          edgesJson: serializeEdges(edges),
          agentName: agentName.trim(),
          shareWithOrg: true,
        });
        setSuccess(t('agentBuilder.serviceAccountConverted'));
      } else {
        const created = await AgentsApi.createAgent(agentConfig);
        invalidateModelsForContext(created._key);
        setServiceAccountConfirmOpen(false);
        setCleanSnapshot({
          nodesJson: serializeNodes(nodes),
          edgesJson: serializeEdges(edges),
          agentName: agentName.trim(),
          shareWithOrg: true,
        });
        router.replace(`/agents/edit?agentKey=${encodeURIComponent(created._key)}&sa=1`);
      }
    } catch (e: unknown) {
      setServiceAccountError(extractErrorMessage(e, t('agentBuilder.svcAcctEnableFailed')));
    } finally {
      setServiceAccountCreating(false);
    }
  }, [
    agentName,
    canPersist,
    edges,
    hasToolsets,
    loadedAgent,
    nodes,
    notifyServiceAccountToolsetBlocked,
    refreshAgent,
    router,
    setCleanSnapshot,
    setSuccess,
    showInlineAgentNameRequired,
    t,
  ]);

  const confirmDelete = useCallback(async () => {
    if (!nodeToDelete || isAgentStructureLocked) return;
    setDeleting(true);
    setNodes((nds) => nds.filter((n) => n.id !== nodeToDelete));
    setEdges((eds) => eds.filter((e) => e.source !== nodeToDelete && e.target !== nodeToDelete));
    setDeleteDialogOpen(false);
    setNodeToDelete(null);
    setDeleting(false);
  }, [
    isAgentStructureLocked,
    nodeToDelete,
    setDeleteDialogOpen,
    setEdges,
    setNodes,
    setDeleting,
  ]);

  const confirmDeleteAgent = useCallback(async () => {
    if (!loadedAgent?._key) return;
    setIsDeletingAgent(true);
    setError(null);
    try {
      await AgentsApi.deleteAgent(loadedAgent._key);
      setAgentDeleteDialogOpen(false);
      router.replace('/chat/');
    } catch (e: unknown) {
      setError(extractErrorMessage(e, t('agentBuilder.deleteAgentFailed')));
    } finally {
      setIsDeletingAgent(false);
    }
  }, [loadedAgent, router, setError, t]);

  // Look up the label of the node pending deletion for the confirmation dialog.
  const nodeToDeleteLabel = useMemo(() => {
    if (!nodeToDelete) return null;
    const node = nodes.find((n) => n.id === nodeToDelete);
    return (node?.data?.label as string) || null;
  }, [nodeToDelete, nodes]);

  const handleGoBack = useCallback(() => {
    if (isDirty) {
      const confirmed = window.confirm(t('agentBuilder.unsavedChangesConfirm'));
      if (!confirmed) return;
    }
    router.push('/chat');
  }, [isDirty, router, t]);

  return (
    <ReactFlowProvider>
      <Flex direction="column" style={{ height: '100%', minHeight: 0, overflow: 'hidden' }}>
        <AgentBuilderHeader
          agentName={agentName}
          onAgentNameChange={setAgentName}
          agentNameError={agentNameError}
          agentNameInputRef={agentNameInputRef}
          saving={saving}
          onSave={handleSave}
          onGoBack={handleGoBack}
          isDirty={isDirty}
          shareWithOrg={shareWithOrg}
          onShareWithOrgChange={setShareWithOrg}
          isFlowStructureLocked={isAgentStructureLocked}
          canPersist={canPersist}
          isServiceAccount={isServiceAccount}
          editing={Boolean(loadedAgent)}
          onEnableServiceAccount={canPersist ? handleRequestServiceAccount : undefined}
          canDeleteAgent={Boolean(loadedAgent?.can_delete)}
          onRequestDeleteAgent={() => setAgentDeleteDialogOpen(true)}
        />

        {((loadedAgent && !canPersist) || error || banner || success) && (
          <Flex
            direction="column"
            gap="2"
            px="4"
            py="3"
            style={{
              flexShrink: 0,
              borderBottom: '1px solid var(--olive-3)',
              background: 'var(--olive-1)',
            }}
          >
            {loadedAgent && !canPersist ? (
              <Callout.Root color="blue" variant="surface" size="1">
                <Callout.Text style={{ flex: 1, minWidth: 0 }}>
                  {isServiceAccount
                    ? t('chat.viewAgentTooltipServiceAccount')
                    : t('chat.viewAgentTooltipIndividual')}
                </Callout.Text>
              </Callout.Root>
            ) : null}
            {error ? (
              <Callout.Root color="red" variant="surface" size="1">
                <Flex align="start" justify="between" gap="3" wrap="wrap">
                  <Callout.Text style={{ flex: 1, minWidth: 0 }}>{error}</Callout.Text>
                  <Button variant="soft" color="gray" size="1" onClick={() => setError(null)}>
                    {t('agentBuilder.dismiss')}
                  </Button>
                </Flex>
              </Callout.Root>
            ) : null}
            {banner ? (
              <Callout.Root color="amber" variant="surface" size="1">
                <Flex align="start" justify="between" gap="3" wrap="wrap">
                  <Callout.Text style={{ flex: 1, minWidth: 0 }}>{banner}</Callout.Text>
                  <Button variant="soft" color="gray" size="1" onClick={() => setBanner(null)}>
                    {t('common.ok')}
                  </Button>
                </Flex>
              </Callout.Root>
            ) : null}
            {success ? (
              <Callout.Root color="green" variant="surface" size="1">
                <Flex align="start" justify="between" gap="3" wrap="wrap">
                  <Callout.Text style={{ flex: 1, minWidth: 0 }}>{success}</Callout.Text>
                  <Button variant="soft" color="gray" size="1" onClick={() => setSuccess(null)}>
                    {t('agentBuilder.dismiss')}
                  </Button>
                </Flex>
              </Callout.Root>
            ) : null}
          </Flex>
        )}

        <Flex style={{ flex: 1, minHeight: 0, minWidth: 0 }}>
          <AgentBuilderSidebar
            open={sidebarOpen}
            width={AGENT_BUILDER_SIDEBAR_WIDTH}
            loading={loading}
            nodeTemplates={nodeTemplates}
            configuredConnectors={configuredConnectors}
            toolsets={toolsets}
            activeToolsetTypeKeys={activeToolsetTypeKeys}
            toolsetMergeCheckNodes={nodes as ToolsetTypeKeyFlowNode[]}
            refreshToolsets={refreshToolsets}
            onNotify={setBanner}
            agentKey={effectiveAgentKey}
            isServiceAccount={isServiceAccount}
            paletteStructureLocked={isAgentStructureLocked}
            paletteDragBlockedMessage={paletteDragBlockedMessage}
            toolsetsOrgCredentialLocked={isServiceAccountToolsetOrgLocked}
            webSearchAttached={webSearchAttached}
            onManageAgentToolsetCredentials={
              isServiceAccountToolsetOrgLocked
                ? undefined
                : (ts) => {
                    if (!effectiveAgentKey) {
                      setBanner(t('agentBuilder.saveAsServiceAccountFirst'));
                      return;
                    }
                    if (!ts.instanceId) return;
                    setAgentToolsetDialog({ toolset: ts, instanceId: ts.instanceId });
                  }
            }
          />
          <AgentBuilderCanvas
            sidebarOpen={sidebarOpen}
            sidebarWidth={AGENT_BUILDER_SIDEBAR_WIDTH}
            nodes={nodes}
            edges={edges}
            onNodesChange={onNodesChange}
            onEdgesChange={onEdgesChange}
            onConnect={onConnect}
            onEdgeClick={onEdgeClick}
            setNodes={setNodes}
            setEdges={setEdges}
            nodeTemplates={nodeTemplates}
            configuredConnectors={configuredConnectors}
            activeAgentConnectors={activeAgentConnectors}
            onNodeDelete={(id) => {
              setNodeToDelete(id);
              setDeleteDialogOpen(true);
            }}
            onError={(m) => setBanner(m)}
            readOnly={isAgentStructureLocked}
          />
        </Flex>

        <Flex
          align="center"
          justify="between"
          px="4"
          py="3"
          gap="3"
          wrap="wrap"
          style={{
            borderTop: '1px solid var(--olive-3)',
            flexShrink: 0,
            background: 'var(--olive-1)',
            fontFamily: 'Manrope, sans-serif',
          }}
        >
          <Button variant="soft" color="gray" onClick={() => setSidebarOpen((s) => !s)}>
            <Flex align="center" gap="2">
              <MaterialIcon
                name={sidebarOpen ? 'arrow_back' : 'arrow_forward'}
                size={18}
                color="var(--slate-11)"
              />
              {sidebarOpen ? t('agentBuilder.hidePalette') : t('agentBuilder.showPalette')}
            </Flex>
          </Button>
          {loadedAgent ? (
            <Button variant="soft" color="green" onClick={() => router.push(buildChatHref({ agentId: loadedAgent._key }))}>
              <Flex align="center" gap="2">
                <MaterialIcon name="chat" size={18} />
                {t('agentBuilder.openInChat')}
              </Flex>
            </Button>
          ) : null}
        </Flex>
      </Flex>

      {/* ── Delete node dialog ── */}
      <Dialog.Root open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
        <Dialog.Content style={{ maxWidth: 400 }}>
          <Dialog.Title>{t('agentBuilder.removeNodeTitle')}</Dialog.Title>
          <Text size="2" mb="3">
            {nodeToDeleteLabel
              ? <>{t('agentBuilder.removeNodeWithName', { name: nodeToDeleteLabel })}</>
              : t('agentBuilder.removeNodeFallback')}
          </Text>
          <Flex gap="2" justify="end">
            <Dialog.Close>
              <Button variant="soft" color="gray">
                {t('action.cancel')}
              </Button>
            </Dialog.Close>
            <Button color="red" onClick={confirmDelete} disabled={deleting}>
              {t('agentBuilder.remove')}
            </Button>
          </Flex>
        </Dialog.Content>
      </Dialog.Root>

      {/* ── Delete edge dialog ── */}
      <Dialog.Root
        open={edgeDeleteDialogOpen}
        onOpenChange={(o) => {
          setEdgeDeleteDialogOpen(o);
          if (!o) setEdgeToDelete(null);
        }}
      >
        <Dialog.Content style={{ maxWidth: 400 }}>
          <Dialog.Title>{t('agentBuilder.removeConnectionTitle')}</Dialog.Title>
          <Text size="2" mb="3">
            {t('agentBuilder.removeConnectionDesc')}
          </Text>
          <Flex gap="2" justify="end">
            <Dialog.Close>
              <Button variant="soft" color="gray">
                {t('action.cancel')}
              </Button>
            </Dialog.Close>
            <Button
              color="red"
              onClick={() => {
                if (edgeToDelete) {
                  setEdges((eds) => eds.filter((e) => e.id !== edgeToDelete));
                }
                setEdgeDeleteDialogOpen(false);
                setEdgeToDelete(null);
              }}
            >
              {t('agentBuilder.remove')}
            </Button>
          </Flex>
        </Dialog.Content>
      </Dialog.Root>

      <ServiceAccountConfirmDialog
        open={serviceAccountConfirmOpen}
        agentName={agentName}
        creating={serviceAccountCreating}
        error={serviceAccountError}
        isConverting={Boolean(loadedAgent)}
        onClose={() => {
          setServiceAccountConfirmOpen(false);
          setServiceAccountError(null);
        }}
        onConfirm={handleConfirmServiceAccount}
      />

      {agentToolsetDialog && effectiveAgentKey ? (
        <AgentToolsetCredentialsDialog
          toolset={agentToolsetDialog.toolset}
          instanceId={agentToolsetDialog.instanceId}
          agentKey={effectiveAgentKey}
          onClose={() => {
            setAgentToolsetDialog(null);
            void refreshToolsets(effectiveAgentKey, true);
          }}
          onSuccess={() => {
            void refreshToolsets(effectiveAgentKey, true);
          }}
          onNotify={setBanner}
        />
      ) : null}

      <DeleteAgentDialog
        open={agentDeleteDialogOpen}
        onOpenChange={setAgentDeleteDialogOpen}
        onConfirm={confirmDeleteAgent}
        agentName={agentName.trim() || loadedAgent?.name || ''}
        isDeleting={isDeletingAgent}
      />

      {/* ── Post-update dialog ── */}
      <Dialog.Root open={showPostUpdateDialog} onOpenChange={setShowPostUpdateDialog}>
        <Dialog.Content style={{ maxWidth: 360 }}>
          <VisuallyHidden><Dialog.Title>{t('agentBuilder.agentUpdated')}</Dialog.Title></VisuallyHidden>
          <Flex direction="column" gap="4">
            <Flex align="center" gap="3">
              <Box
                style={{
                  width: 36,
                  height: 36,
                  borderRadius: 'var(--radius-2)',
                  display: 'flex',
                  alignItems: 'center',
                  justifyContent: 'center',
                  background: 'var(--green-3)',
                  border: '1px solid var(--green-6)',
                  flexShrink: 0,
                }}
              >
                <MaterialIcon name="check_circle" size={20} style={{ color: 'var(--green-11)' }} />
              </Box>
              <Box>
                <Text size="3" weight="bold" style={{ color: 'var(--olive-12)', display: 'block', lineHeight: 1.3 }}>
                  {t('agentBuilder.agentUpdated')}
                </Text>
                <Text size="2" style={{ color: 'var(--olive-11)', display: 'block', lineHeight: 1.4 }}>
                  {t('agentBuilder.agentUpdatedDesc')}
                </Text>
              </Box>
            </Flex>
            <Flex gap="2" justify="end">
              <Button
                variant="soft"
                color="gray"
                size="2"
                onClick={() => setShowPostUpdateDialog(false)}
              >
                {t('agentBuilder.continueEditing')}
              </Button>
              <Button
                size="2"
                color="jade"
                onClick={() => {
                  setShowPostUpdateDialog(false);
                  if (loadedAgent) router.push(buildChatHref({ agentId: loadedAgent._key }));
                }}
              >
                <Flex align="center" gap="2">
                  <MaterialIcon name="chat" size={16} />
                  {t('agentBuilder.openInChat')}
                </Flex>
              </Button>
            </Flex>
          </Flex>
        </Dialog.Content>
      </Dialog.Root>
    </ReactFlowProvider>
  );
}
