"use client";

import { useTheme } from "@/components/theme-provider";
import { isLightColorTheme } from "@/lib/color-theme";
import { getErrorMessage } from "@/lib/error-utils";
import type { Extension } from "@codemirror/state";

import {
  AiReviewButton,
  AiReviewPanel,
  buildBlockingMessage,
  buildLastReview,
  useAiReview,
} from "@/components/ai-review";
import { TeamOwnershipFields } from "@/components/rbac/TeamOwnershipFields";
import { HarnessFeaturePanels } from "@/components/harness-engine/HarnessFeaturePanels";
import { ClaudeACPSettings } from "@/components/harness-engine/ClaudeACPSettings";
import { HarnessHelp } from "@/components/harness-engine/HarnessHelp";
import { harnessBuilderSections, harnessDisplayName, harnessStatusLabel, supportsHarnessCapability, validateHarnessOptionsSchema } from "@/lib/harness-builder";
import { HarnessOptionsForm } from "@/components/harness-engine/HarnessOptionsForm";
import { UnsavedChangesDialog } from "@/components/shared/UnsavedChangesDialog";
import { Button } from "@/components/ui/button";
import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { ModelPicker } from "@/components/ui/model-picker";
import { Select } from "@/components/ui/select";
import { Switch } from "@/components/ui/switch";
import { type TeamPickerOption } from "@/components/ui/team-picker";
import { Textarea } from "@/components/ui/textarea";
import {
  Tooltip,
  TooltipContent,
  TooltipProvider,
  TooltipTrigger,
} from "@/components/ui/tooltip";
import { useToast } from "@/components/ui/toast";
import { useEditorDirtyTracking } from "@/hooks/use-editor-dirty-tracking";
import { gradientThemes } from "@/lib/gradient-themes";
import { getMarkdownComponents } from "@/lib/markdown-components";
import { cn } from "@/lib/utils";
import { useUnsavedChangesStore } from "@/store/unsaved-changes-store";
import type {
  AgentUIConfig,
  BuiltinToolsConfig,
  CustomThemeConfig,
  DynamicAgentConfig,
  DynamicAgentConfigCreate,
  DynamicAgentConfigUpdate,
  FeaturesConfig,
  InterruptOn,
  ReasoningEffort,
  SubAgentRef,
  VisibilityType,
} from "@/types/dynamic-agent";
import type { AgentBlueprint, HarnessDescriptor } from "@/types/harness-engine";
import { AnimatePresence, motion } from "framer-motion";
import {
  ArrowLeft,
  Check,
  ChevronDown,
  ChevronLeft,
  ChevronRight,
  Eye,
  Globe,
  GripHorizontal,
  Info,
  Loader2,
  Pencil,
  Sparkles,
  Users,
} from "lucide-react";
import React from "react";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import { AgentAvatar } from "./AgentAvatar";
import { AllowedToolsPicker } from "./AllowedToolsPicker";
import { BuiltinToolsPicker } from "./BuiltinToolsPicker";
import { ConnectAgentCoreDialog, useAgentCoreConnectionAccess } from "./ConnectAgentCoreDialog";
import { DatasourcePicker } from "./DatasourcePicker";
import { InterruptConfigPicker } from "./InterruptConfigPicker";
import { MiddlewarePicker } from "./MiddlewarePicker";
import { SkillsSelector } from "./SkillsSelector";
import { SubagentPicker } from "./SubagentPicker";
import { WorkflowToolsPicker } from "./WorkflowToolsPicker";
import { RemoteAgentsPicker } from "./RemoteAgentsPicker";
import type { AgentSetupStep } from "./deep-linking";

// Lazy-load CodeMirror to avoid SSR issues
const CodeMirrorEditor = React.lazy(() => import("@uiw/react-codemirror"));

interface DynamicAgentEditorProps {
  agent: DynamicAgentConfig | null; // null = creating new
  cloneFrom?: DynamicAgentConfig | null; // Agent to clone from (for pre-filling)
  readOnly?: boolean; // true for view-only mode
  readOnlyReason?: "config" | "permissions"; // why readOnly is true
  initialStep?: AgentSetupStep;
  onStepChange?: (step: AgentSetupStep) => void;
  onSave: () => void;
  onCancel: () => void;
}
/**
 * Generate a URL-safe slug from an agent name with agent- prefix.
 * e.g., "Knowledge Agent" -> "agent-knowledge-agent"
 */
function generateSlug(name: string): string {
  const slug = name
    .toLowerCase()
    .trim()
    .replace(/[^a-z0-9\s-]/g, "") // Remove special characters
    .replace(/\s+/g, "-") // Replace spaces with hyphens
    .replace(/-+/g, "-") // Collapse multiple hyphens
    .replace(/^-|-$/g, ""); // Trim leading/trailing hyphens
  return slug ? `agent-${slug}` : "";
}

function hasRagToolAccess(
  allowedTools: Record<string, string[] | boolean>,
): boolean {
  const selection = allowedTools["knowledge-base"];
  return selection !== undefined && selection !== false;
}

function normalizeKnowledgeIds(value: unknown): string[] {
  if (!Array.isArray(value)) return [];
  return value.filter(
    (item): item is string => typeof item === "string" && Boolean(item.trim()),
  );
}

// Visibility picker — `private` was retired on 2026-05-22 (see
// `docs/docs/changes/2026-05-22-remove-private-agents.md` and the
// `VisibilityType` definition in `@/types/dynamic-agent`). Every dynamic
// agent is now team-owned; users who want a truly personal agent should
// create a single-member team and own the agent through that team.
const VISIBILITY_OPTIONS: {
  value: VisibilityType;
  label: string;
  icon: React.ReactNode;
  description: string;
}[] = [
  {
    value: "team",
    label: "Team",
    icon: <Users className="h-4 w-4" />,
    description:
      "Team members can use; you manage as creator; team admins can manage. Optionally share with other teams.",
  },
  {
    value: "global",
    label: "Global",
    icon: <Globe className="h-4 w-4" />,
    description: "Available to all users; owner-team admins manage it.",
  },
];

interface TeamOption {
  _id: string;
  name: string;
  slug?: string;
  description?: string;
  user_role?: string | null;
  can_own_agents?: boolean;
}

// Step definitions for the wizard
const STEPS = [
  {
    id: "basic" as const,
    label: "Basic Info",
    hint: "Define your agent's identity and access level",
  },
  {
    id: "instructions" as const,
    label: "Instructions",
    hint: "Configure how your agent behaves",
  },
  {
    id: "tools" as const,
    label: "Tools",
    hint: "Select which tools your agent can use",
  },
  {
    id: "knowledge" as const,
    label: "Knowledge",
    hint: "Choose the collections and datasources this agent can search",
  },
  {
    id: "skills" as const,
    label: "Skills",
    hint: "Attach skills that guide your agent's behavior (optional)",
  },
  {
    id: "advanced" as const,
    label: "Advanced",
    hint: "Subagents, remote agents, approval rules, and middleware",
  },
];

type StepId = AgentSetupStep;

/**
 * Horizontal step indicator component
 */
function StepIndicator({
  steps,
  currentStep,
  onStepClick,
}: {
  steps: typeof STEPS;
  currentStep: StepId;
  onStepClick: (stepId: StepId) => void;
}) {
  return (
    <div className="grid w-full max-w-full grid-cols-3 gap-x-1 gap-y-1 lg:ml-auto lg:flex lg:w-auto lg:items-center lg:justify-end lg:gap-x-0">
      {steps.map((step, index) => (
        <React.Fragment key={step.id}>
          {index > 0 && (
            <div className="mx-0.5 hidden h-0.5 w-5 bg-border lg:block" />
          )}
          <button
            type="button"
            onClick={() => onStepClick(step.id)}
            className={cn(
              "flex min-w-[64px] flex-col items-center gap-0.5 rounded-md px-1.5 py-1.5 transition-colors lg:px-2.5",
              currentStep === step.id
                ? "bg-primary/10 text-primary"
                : "hover:bg-muted text-muted-foreground",
            )}
          >
            <div
              className={cn(
                "w-6 h-6 rounded-full flex items-center justify-center text-xs font-medium",
                currentStep === step.id
                  ? "bg-primary text-primary-foreground"
                  : "bg-muted",
              )}
            >
              {index + 1}
            </div>
            <span className="text-xs font-medium">{step.label}</span>
          </button>
        </React.Fragment>
      ))}
    </div>
  );
}

/**
 * Collapsible sub-section used in the Advanced step.
 */
function CollapsibleSection({
  title,
  description,
  badge,
  defaultExpanded = false,
  children,
}: {
  title: string;
  description: string;
  badge?: string;
  defaultExpanded?: boolean;
  children: React.ReactNode;
}) {
  const [expanded, setExpanded] = React.useState(defaultExpanded);

  return (
    <div className="border rounded-lg">
      <button
        type="button"
        onClick={() => setExpanded(!expanded)}
        className="flex items-center gap-2 w-full text-left px-4 py-3 hover:bg-muted/50 transition-colors rounded-lg"
      >
        {expanded ? (
          <ChevronDown className="h-4 w-4 text-muted-foreground shrink-0" />
        ) : (
          <ChevronRight className="h-4 w-4 text-muted-foreground shrink-0" />
        )}
        <div className="flex-1">
          <span className="text-sm font-semibold">{title}</span>
          <p className="text-xs text-muted-foreground">{description}</p>
        </div>
        {badge && (
          <span className="text-xs text-muted-foreground font-medium">
            {badge}
          </span>
        )}
      </button>
      {expanded && <div className="px-4 pb-4 pt-1">{children}</div>}
    </div>
  );
}

/**
 * Advanced step: collapsible sub-sections for subagents, interrupts, middleware.
 */
function AdvancedStep({
  agent,
  subagents,
  setSubagents,
  allowedRemoteAgents,
  setAllowedRemoteAgents,
  remoteAgentTimeouts,
  setRemoteAgentTimeouts,
  interruptOn,
  setInterruptOn,
  allowedTools,
  builtinTools,
  setBuiltinTools,
  features,
  setFeatures,
  availableModels,
  setMiddlewareError,
  loading,
  visibility,
  descriptor,
  schemaPanel,
}: {
  descriptor?: HarnessDescriptor;
  schemaPanel?: React.ReactNode;
  agent: DynamicAgentConfig | null;
  subagents: SubAgentRef[];
  setSubagents: (v: SubAgentRef[]) => void;
  allowedRemoteAgents: string[];
  setAllowedRemoteAgents: (v: string[]) => void;
  remoteAgentTimeouts: Record<string, number>;
  setRemoteAgentTimeouts: React.Dispatch<React.SetStateAction<Record<string, number>>>;
  interruptOn: InterruptOn;
  setInterruptOn: (v: InterruptOn) => void;
  allowedTools: Record<string, string[] | boolean>;
  builtinTools?: BuiltinToolsConfig;
  setBuiltinTools: (v: BuiltinToolsConfig) => void;
  features: FeaturesConfig | undefined;
  setFeatures: (v: FeaturesConfig | undefined) => void;
  availableModels: { model_id: string; name: string; provider: string }[];
  setMiddlewareError: (v: boolean) => void;
  loading: boolean;
  visibility: VisibilityType;
}) {
  const interruptRuleCount = Object.values(interruptOn).reduce(
    (sum, tools) => sum + Object.keys(tools).length,
    0,
  );
  const workflowCount = builtinTools?.workflows?.length ?? 0;
  const remoteAgentCount = allowedRemoteAgents.length;

  const panels = {
    "schema-form": schemaPanel,
    "agent-delegation": (<CollapsibleSection
        title="Subagents"
        description="Delegate tasks to other agents"
        badge={`${subagents.length} subagent${subagents.length !== 1 ? "s" : ""}`}
        defaultExpanded={false}
      >
        <p className="text-xs text-muted-foreground mb-2">
          <span className="font-medium">Note:</span> Subagents cannot be nested.
          The agents you add here will not have access to their own subagents
          when invoked.
        </p>
        <SubagentPicker
          agentId={agent?._id || null}
          value={subagents}
          onChange={setSubagents}
          disabled={loading}
          parentVisibility={visibility}
        />
      </CollapsibleSection>),
    "remote-agents": (<CollapsibleSection
        title="Remote A2A Agents"
        description="Call agents registered with the A2A protocol"
        badge={`${remoteAgentCount} selected`}
        defaultExpanded
      >
        <RemoteAgentsPicker
          value={allowedRemoteAgents}
          onChange={(ids) => {
          setAllowedRemoteAgents(ids);
          setRemoteAgentTimeouts((current) => Object.fromEntries(
            Object.entries(current).filter(([id]) => ids.includes(id)),
          ));
        }}
          timeoutValues={remoteAgentTimeouts}
          onTimeoutChange={(id, seconds) =>
            setRemoteAgentTimeouts((current) => ({ ...current, [id]: seconds }))
          }
          disabled={loading}
        />
      </CollapsibleSection>),
    "tool-approvals": (<CollapsibleSection
        title="Human Approval"
        description="Require approval before executing specific tools"
        badge={`${interruptRuleCount} rule${interruptRuleCount !== 1 ? "s" : ""}`}
        defaultExpanded={false}
      >
        <InterruptConfigPicker
          value={interruptOn}
          onChange={setInterruptOn}
          allowedTools={allowedTools}
          builtinTools={builtinTools}
          disabled={loading}
        />
      </CollapsibleSection>),
    "deep-agents-middleware": (<CollapsibleSection
        title="Middleware"
        description="Retries, limits, and preprocessing"
      >
        <MiddlewarePicker
          value={features}
          onChange={setFeatures}
          disabled={loading}
          availableModels={availableModels}
          onError={setMiddlewareError}
        />
      </CollapsibleSection>),
    "workflow-tools": (<CollapsibleSection
        title="Workflows"
        description="Allow this agent to trigger and monitor workflows"
        badge={`${workflowCount} workflow${workflowCount !== 1 ? "s" : ""}`}
        defaultExpanded={false}
      >
        <WorkflowToolsPicker
          value={builtinTools?.workflows ?? []}
          onChange={(ids) => {
            setBuiltinTools({
              ...builtinTools,
              workflows: ids.length > 0 ? ids : undefined,
            } as BuiltinToolsConfig);
          }}
          disabled={loading}
        />
      </CollapsibleSection>)
  };
  return <div className="space-y-4 pt-2"><HarnessFeaturePanels descriptor={descriptor} step="advanced" panels={panels} /></div>;
}

export function DynamicAgentEditor({
  agent,
  cloneFrom,
  readOnly,
  readOnlyReason,
  initialStep = "basic",
  onStepChange,
  onSave,
  onCancel,
}: DynamicAgentEditorProps) {
  const isEditing = !!agent;
  const isCloning = !!cloneFrom;
  const { toast } = useToast();
  const connectionConfigured = useAgentCoreConnectionAccess();
  const [connectionDialogOpen, setConnectionDialogOpen] = React.useState(false);

  // Source for initial values: editing agent > cloning source > empty defaults
  const source = agent || cloneFrom;

  // Form state - when cloning, append " (New)" to name
  const [name, setName] = React.useState(
    isCloning && source ? `${source.name} (New)` : source?.name || "",
  );
  const [description, setDescription] = React.useState(
    source?.description || "",
  );
  const [systemPrompt, setSystemPrompt] = React.useState(
    source?.system_prompt || "",
  );
  // Default to `team` for new agents — every agent must have an owner
  // team, and team-scoped sharing is the safest default. `private` is
  // retired (see `VisibilityType` in `@/types/dynamic-agent`); legacy
  // docs that still carry `visibility: 'private'` on the wire are coerced
  // to `team` here so the picker has a matching tile to highlight. The
  // BFF-side `coerceAgentVisibilityOnRead` helper does the same on read,
  // but we coerce defensively in the UI in case a stale GET response
  // slips through before that helper is wired into every route.
  const [visibility, setVisibility] = React.useState<VisibilityType>(() => {
    const raw = source?.visibility as VisibilityType | "private" | undefined;
    if (raw === "team" || raw === "global") return raw;
    return "team";
  });
  const [sharedWithTeams, setSharedWithTeams] = React.useState<string[]>(
    source?.shared_with_teams || [],
  );
  const [ownerTeamSlug, setOwnerTeamSlug] = React.useState(
    source?.owner_team_slug || "",
  );
  // Ownership transfer (spec 2026-06-03, US3). On edit, changing the owner
  // picker marks a pending transfer so the PUT can send owner_team_slug +
  // confirm_not_member.
  const [transferRequested, setTransferRequested] = React.useState(false);
  const [transferConfirmedNotMember, setTransferConfirmedNotMember] =
    React.useState(false);
  // When the server rejects a transfer with TRANSFER_NOT_MEMBER_UNCONFIRMED
  // (the caller is not a member of the destination team per OpenFGA, even
  // though the client-side picker showed it — e.g. an org admin who is not a
  // literal team member), surface an inline "Confirm Transfer" affordance
  // instead of a dead-end error so the user can explicitly proceed.
  const [transferNeedsServerConfirm, setTransferNeedsServerConfirm] =
    React.useState(false);
  const [allowedTools, setAllowedTools] = React.useState<
    Record<string, string[] | boolean>
  >(source?.allowed_tools || {});
  const [allowedRemoteAgents, setAllowedRemoteAgents] = React.useState<string[]>(source?.allowed_remote_agents || []);
  const [remoteAgentTimeouts, setRemoteAgentTimeouts] = React.useState<Record<string, number>>(source?.remote_agent_timeouts || {});
  const [builtinTools, setBuiltinTools] = React.useState<
    BuiltinToolsConfig | undefined
  >(source?.builtin_tools);
  const [subagents, setSubagents] = React.useState<SubAgentRef[]>(
    source?.subagents || [],
  );
  const [skills, setSkills] = React.useState<string[]>(source?.skills || []);
  const [datasourceIds, setDatasourceIds] = React.useState<string[]>(() =>
    normalizeKnowledgeIds(source?.datasource_ids),
  );
  const [ragCollectionIds, setRagCollectionIds] = React.useState<string[]>(() =>
    normalizeKnowledgeIds(source?.rag_collection_ids),
  );
  // An agent with neither field configured is unrestricted: it searches
  // whatever the calling user can already access, with no additional
  // agent-level narrowing. This toggle is how the editor distinguishes that
  // intentional default from "restricted, but to nothing" (both fields
  // present as explicit empty arrays). Default a brand-new agent to
  // unrestricted; preserve whatever an existing agent already has.
  const [restrictKnowledge, setRestrictKnowledge] = React.useState<boolean>(
    () => source?.datasource_ids != null || source?.rag_collection_ids != null,
  );
  // Stable snapshot of whether this agent had an explicit restriction when
  // the editor opened, so turning the toggle off only sends a clearing
  // `null` when there is actually something to clear.
  const hadExplicitKnowledgeScope = React.useRef(restrictKnowledge).current;
  const [features, setFeatures] = React.useState<FeaturesConfig | undefined>(
    source?.features,
  );
  const [interruptOn, setInterruptOn] = React.useState<InterruptOn>(
    source?.interrupt_on || { builtin: { request_user_input: true } },
  );
  const [modelId, setModelId] = React.useState(source?.model?.id || "");
  const [modelProvider, setModelProvider] = React.useState(
    source?.model?.provider || "",
  );
  const [reasoningEffort, setReasoningEffort] = React.useState<ReasoningEffort>(
    source?.model?.reasoning_effort || "medium",
  );
  const [modelStreaming, setModelStreaming] = React.useState<boolean | null>(
    source?.model?.streaming ?? null,
  );
  const [supportedReasoningEfforts, setSupportedReasoningEfforts] = React.useState<ReasoningEffort[] | null>(null);
  const [gradientTheme, setGradientTheme] = React.useState<string>(
    source?.ui?.gradient_theme || "default",
  );
  const [customThemeConfig, setCustomThemeConfig] =
    React.useState<CustomThemeConfig>(
      source?.ui?.custom_theme_config || {
        gradient_from: "#6366f1",
        gradient_to: "#1e1b4b",
        accent_color: "#ffffff",
      },
    );
  const [showCustomPicker, setShowCustomPicker] = React.useState(false);
  // Harness Engine persists a complete, immutable blueprint independently of
  // the legacy Dynamic Agents document. The catalog drives this form.
  const [harnessId, setHarnessId] = React.useState(
    source?.execution_harness_id || "dynamic_agents",
  );
  const [harnessProfileId, setHarnessProfileId] = React.useState("");
  const [harnessOptions, setHarnessOptions] = React.useState<Record<string, unknown>>({});
  const [harnessDescriptors, setHarnessDescriptors] = React.useState<HarnessDescriptor[]>([]);
  const [catalogRevision, setCatalogRevision] = React.useState<string | null>(null);
  const [harnessRevision, setHarnessRevision] = React.useState<number | null>(null);
  const [harnessLoading, setHarnessLoading] = React.useState(true);
  const [harnessLoadError, setHarnessLoadError] = React.useState<string | null>(null);
  const harnessDrafts = React.useRef(
    new Map<string, { profileId: string; options: Record<string, unknown> }>(),
  );
  const selectedHarness = React.useMemo(
    () => harnessDescriptors.find((descriptor) => descriptor.id === harnessId),
    [harnessDescriptors, harnessId],
  );
  const isDeepAgents = harnessId === "dynamic_agents";
  const [harnessSwitchConfirmed, setHarnessSwitchConfirmed] = React.useState(false);
  const requiresHarnessConfirmation = isEditing && harnessId !== (agent?.execution_harness_id || "dynamic_agents");
  const supportsTools = supportsHarnessCapability(selectedHarness, "tools.broker", isDeepAgents);
  const supportsKnowledge = supportsHarnessCapability(selectedHarness, "knowledge", isDeepAgents);
  const supportsSkills = supportsHarnessCapability(selectedHarness, "skills", isDeepAgents);
  const supportsConfiguredModel = supportsHarnessCapability(selectedHarness, "model.configured", isDeepAgents);
  const supportsBuiltinTools = supportsHarnessCapability(selectedHarness, "tools.builtin", isDeepAgents);
  const supportsDelegation = supportsHarnessCapability(selectedHarness, "multi_agent.delegation", isDeepAgents);
  const supportsRemoteAgents = supportsHarnessCapability(selectedHarness, "remote_agents", isDeepAgents);
  const supportsApprovals = supportsHarnessCapability(selectedHarness, "human_input.resume", isDeepAgents);
  const supportsMiddleware = supportsHarnessCapability(selectedHarness, "deep_agents.middleware", isDeepAgents);

  let builderSchemaError: string | null = null;
  let hasFeaturePanels = false;
  try { if (selectedHarness) validateHarnessOptionsSchema(selectedHarness.options_schema); hasFeaturePanels = harnessBuilderSections(selectedHarness).some(section => section.step === "advanced" && section.renderer !== "schema-form"); }
  catch (error) { builderSchemaError = error instanceof Error ? error.message : "Unsupported editor schema"; }
  const hasHarnessOptions =
    Object.keys(selectedHarness?.options_schema.properties ?? {}).length > 0;
  const steps = STEPS.filter((step) => {
    if (step.id === "tools") return supportsTools;
    if (step.id === "knowledge") return supportsKnowledge;
    if (step.id === "skills") return supportsSkills;
    if (step.id === "advanced") return isDeepAgents || hasFeaturePanels || hasHarnessOptions;
    return true;
  }).map((step) =>
    step.id === "advanced" && !isDeepAgents
      ? { ...step, hint: "Configure options supported by this harness" }
      : step,
  );

  const selectHarness = React.useCallback((nextHarnessId: string) => {
    harnessDrafts.current.set(harnessId, {
      profileId: harnessProfileId,
      options: harnessOptions,
    });
    setHarnessSwitchConfirmed(false);
    setHarnessId(nextHarnessId);
    const draft = harnessDrafts.current.get(nextHarnessId);
    setHarnessOptions(draft?.options ?? {});
    const descriptor = harnessDescriptors.find((item) => item.id === nextHarnessId);
    setHarnessProfileId(draft?.profileId ?? descriptor?.profiles.find((profile) => profile.available)?.id ?? "");
  }, [harnessDescriptors, harnessId, harnessOptions, harnessProfileId]);

  async function selectConnectedAgentCore(profileId: string) {
    const response = await fetch("/api/harness-engine/harnesses", { cache: "no-store" });
    const result = await response.json().catch(() => null);
    const descriptors = result?.data?.harnesses as HarnessDescriptor[] | undefined;
    if (!response.ok || !result?.success || !Array.isArray(descriptors) ||
        !descriptors.some((item) => item.id === "agentcore" && item.availability === "available" && item.certification !== "blocked" && item.profiles.some((profile) => profile.id === profileId && profile.available))) {
      throw new Error("Connection was registered, but its profile could not be loaded. Reopen the editor to select it.");
    }
    harnessDrafts.current.set(harnessId, { profileId: harnessProfileId, options: harnessOptions });
    setHarnessDescriptors(descriptors);
    setCatalogRevision(result.data.catalog_revision);
    setHarnessId("agentcore");
    setHarnessProfileId(profileId);
    setHarnessOptions({});
    setHarnessLoadError(null);
    toast("AgentCore connection selected. Save this agent to use it.", "success");
  }

  // Sync request_user_input interrupt rule with builtin tool enabled state
  const hasRequestUserInputInterrupt =
    !!interruptOn?.builtin?.request_user_input;
  React.useEffect(() => {
    const cfg = (
      builtinTools as Record<string, { enabled?: boolean } | undefined>
    )?.["request_user_input"];
    const isEnabled = !!(cfg && cfg.enabled);
    const hasRule = hasRequestUserInputInterrupt;

    if (isEnabled && !hasRule) {
      // Tool enabled — add the rule
      setInterruptOn((prev) => ({
        ...prev,
        builtin: { ...prev.builtin, request_user_input: true },
      }));
    } else if (!isEnabled && hasRule) {
      // Tool disabled — remove the rule
      setInterruptOn((prev) => {
        const next = { ...prev };
        if (next.builtin) {
          const rest = { ...next.builtin };
          delete rest.request_user_input;
          if (Object.keys(rest).length === 0) {
            delete next.builtin;
          } else {
            next.builtin = rest;
          }
        }
        return next;
      });
    }
  }, [builtinTools, hasRequestUserInputInterrupt]);

  const [loading, setLoading] = React.useState(false);
  const [error, setError] = React.useState<string | null>(null);
  // Blocking-review message, kept separate from `error` so it only renders on
  // the Instructions step and can auto-clear the moment the review passes.
  const [blockingMessage, setBlockingMessage] = React.useState<string | null>(
    null,
  );
  const [, setMiddlewareError] = React.useState(false);
  const [availableModels, setAvailableModels] = React.useState<
    { model_id: string; name: string; provider: string; description: string }[]
  >([]);
  const [modelsLoading, setModelsLoading] = React.useState(false);
  const [availableTeams, setAvailableTeams] = React.useState<TeamOption[]>([]);

  // AI suggestion state
  const [generatingField, setGeneratingField] = React.useState<string | null>(
    null,
  );
  const [promptTab, setPromptTab] = React.useState<"edit" | "preview">("edit");
  const [editorHeight, setEditorHeight] = React.useState(480);
  const dragRef = React.useRef<{ startY: number; startHeight: number } | null>(
    null,
  );
  const [showSuggestPromptInput, setShowSuggestPromptInput] =
    React.useState(false);
  const [suggestPromptInstruction, setSuggestPromptInstruction] =
    React.useState("");
  const [showSuggestBasicInput, setShowSuggestBasicInput] =
    React.useState(false);
  const [suggestBasicInstruction, setSuggestBasicInstruction] =
    React.useState("");
  const [enhanceExisting, setEnhanceExisting] = React.useState(false);
  const [enhanceExistingBasic, setEnhanceExistingBasic] = React.useState(false);
  const [promptStyle, setPromptStyle] = React.useState<
    "concise" | "comprehensive"
  >("concise");

  // AI Review hook for the system prompt (Instructions step). The hook is a no-op
  // when `/api/review-configs/agent-system-prompt` is not configured / disabled —
  // both the button and panel render null in that case.
  const review = useAiReview({
    target: "agent-system-prompt",
    content: systemPrompt,
    context: {
      name,
      agent_description: description,
      extra_context: undefined,
    },
    onApplyFix: setSystemPrompt,
  });

  // Clear the blocking-review banner once the review passes — including a
  // re-run triggered from the panel (apply-all-fixes → run again), not just a
  // Next/Save click.
  React.useEffect(() => {
    if (review.isPassed) setBlockingMessage(null);
  }, [review.isPassed]);

  // Editor resize drag handlers
  const handleDragStart = React.useCallback(
    (e: React.MouseEvent) => {
      e.preventDefault();
      dragRef.current = { startY: e.clientY, startHeight: editorHeight };

      const handleDragMove = (ev: MouseEvent) => {
        if (!dragRef.current) return;
        const delta = ev.clientY - dragRef.current.startY;
        const newHeight = Math.max(
          200,
          Math.min(
            window.innerHeight * 0.85,
            dragRef.current.startHeight + delta,
          ),
        );
        setEditorHeight(newHeight);
      };

      const handleDragEnd = () => {
        dragRef.current = null;
        document.removeEventListener("mousemove", handleDragMove);
        document.removeEventListener("mouseup", handleDragEnd);
        document.body.style.cursor = "";
        document.body.style.userSelect = "";
      };

      document.addEventListener("mousemove", handleDragMove);
      document.addEventListener("mouseup", handleDragEnd);
      document.body.style.cursor = "row-resize";
      document.body.style.userSelect = "none";
    },
    [editorHeight],
  );

  // CodeMirror extensions for markdown syntax highlighting
  const { resolvedTheme } = useTheme();
  const editorIsLight = isLightColorTheme(resolvedTheme);
  const [cmExtensions, setCmExtensions] = React.useState<Extension[]>([]);
  React.useEffect(() => {
    let cancelled = false;
    Promise.all([
      import("@codemirror/lang-markdown"),
      import("@codemirror/language-data"),
      import("@codemirror/view"),
      import("@/lib/codemirror/jinja2-highlight"),
      import("@/lib/codemirror/markdown-highlight"),
    ]).then(([mdMod, langDataMod, viewMod, jinja2Mod, mdHighlightMod]) => {
      if (!cancelled) {
        setCmExtensions([
          mdMod.markdown({ codeLanguages: langDataMod.languages }),
          viewMod.EditorView.lineWrapping,
          editorIsLight
            ? mdHighlightMod.markdownHighlightLight
            : mdHighlightMod.markdownHighlight,
          jinja2Mod.jinja2Highlight,
        ].filter(Boolean));
      }
    });
    return () => {
      cancelled = true;
    };
  }, [editorIsLight]);

  // ID generation and validation
  const generatedId = React.useMemo(() => generateSlug(name), [name]);
  const [existingIds, setExistingIds] = React.useState<Set<string>>(new Set());

  // Check if the generated ID clashes with existing agents
  const idClash = React.useMemo(() => {
    if (isEditing) return false; // When editing, ID doesn't change
    if (!generatedId) return false;
    return existingIds.has(generatedId);
  }, [isEditing, generatedId, existingIds]);

  // Fetch existing agent IDs for clash detection
  React.useEffect(() => {
    if (isEditing) return; // No need to check when editing

    async function fetchExistingIds() {
      try {
        const response = await fetch("/api/dynamic-agents");
        const data = await response.json();
        // API returns paginated response: {success, data: {items: [...], ...}}
        if (
          data.success &&
          data.data?.items &&
          Array.isArray(data.data.items)
        ) {
          const ids = new Set<string>(
            data.data.items.map((a: DynamicAgentConfig) => a._id),
          );
          setExistingIds(ids);
        }
      } catch (err) {
        console.error("Failed to fetch existing agent IDs:", err);
      }
    }
    fetchExistingIds();
  }, [isEditing]);

  React.useEffect(() => {
    if (!modelId || !modelProvider) {
      setSupportedReasoningEfforts([]);
      return;
    }
    let cancelled = false;
    setSupportedReasoningEfforts(null);
    const params = new URLSearchParams({
      model_id: modelId,
      provider: modelProvider,
    });
    fetch(`/api/dynamic-agents/model-capabilities?${params}`)
      .then((response) => (response.ok ? response.json() : null))
      .then((data) => {
        if (!cancelled) {
          setSupportedReasoningEfforts(data?.reasoning_efforts ?? []);
        }
      })
      .catch(() => {
        if (!cancelled) setSupportedReasoningEfforts([]);
      });
    return () => {
      cancelled = true;
    };
  }, [modelId, modelProvider]);

  React.useEffect(() => {
    let cancelled = false;
    async function fetchHarnessConfiguration() {
      try {
        const catalogResponse = await fetch("/api/harness-engine/harnesses");
        const catalog = await catalogResponse.json();
        if (!catalogResponse.ok || !catalog.success) {
          throw new Error(catalog.error || "Harness catalog could not be loaded");
        }
        const descriptors = Array.isArray(catalog.data?.harnesses)
          ? catalog.data.harnesses as HarnessDescriptor[]
          : [];
        if (cancelled) return;
        setHarnessDescriptors(descriptors);
        setCatalogRevision(catalog.data?.catalog_revision ?? null);

        const persistedHarnessId = source?.execution_harness_id;
        const defaultDescriptor = descriptors.find(item => item.id === (persistedHarnessId || "dynamic_agents"));
        setHarnessProfileId(defaultDescriptor?.profiles.find(profile => profile.available)?.id ?? "");
        if (persistedHarnessId) setHarnessId(persistedHarnessId);
        if (source?._id && persistedHarnessId && persistedHarnessId !== "dynamic_agents") {
          const overlayResponse = await fetch(
            `/api/harness-engine/agents/${encodeURIComponent(source._id)}`,
          );
          const overlay = await overlayResponse.json();
          if (!overlayResponse.ok || !overlay.success) {
            throw new Error(overlay.error || "Saved harness configuration could not be loaded");
          }
          const saved = overlay.data?.version?.blueprint;
          if (!saved?.harness?.id) {
            throw new Error("Saved harness configuration is missing");
          }
          if (!cancelled) {
            setHarnessId(saved.harness.id);
            setHarnessProfileId(saved.harness.profile_id);
            setHarnessOptions(saved.harness.options ?? {});
            setHarnessRevision(overlay.data.record?.revision ?? null);
          }
        }
      } catch (err) {
        // Harness Engine is opt-in. Its absence must not affect the existing
        // Dynamic Agents creation/edit path.
        console.info("Harness configuration could not be loaded", err);
        if (!cancelled && source?.execution_harness_id && source.execution_harness_id !== "dynamic_agents") {
          setHarnessLoadError(getErrorMessage(err, "Saved harness configuration could not be loaded"));
        }
      } finally {
        if (!cancelled) setHarnessLoading(false);
      }
    }
    void fetchHarnessConfiguration();
    return () => {
      cancelled = true;
    };
  }, [source?._id, source?.execution_harness_id]);

  // Fetch available models on mount
  React.useEffect(() => {
    async function fetchModels() {
      setModelsLoading(true);
      try {
        const response = await fetch("/api/dynamic-agents/models");
        const data = await response.json();
        if (data.success && Array.isArray(data.data)) {
          setAvailableModels(data.data);

          if (source?.model?.id) {
            // Editing or cloning existing agent - verify model exists using both model AND provider
            // (same model can exist for different providers, e.g., gpt-4o for openai and azure-openai)
            const existingModel = data.data.find(
              (m: { model_id: string; provider: string }) =>
                m.model_id === source.model.id &&
                m.provider === source.model.provider,
            );
            if (existingModel) {
              // Model exists - ensure provider is in sync with config
              setModelProvider(existingModel.provider);
            } else {
              // Model no longer available - reset to first available
              console.warn(
                `Agent model "${source.model.id}" no longer available, resetting to default`,
              );
              if (data.data.length > 0) {
                setModelId(data.data[0].model_id);
                setModelProvider(data.data[0].provider);
              }
            }
          } else if (data.data.length > 0) {
            // Creating new agent - default to first model
            setModelId(data.data[0].model_id);
            setModelProvider(data.data[0].provider);
          }
        }
      } catch (err) {
        console.error("Failed to fetch models:", err);
      } finally {
        setModelsLoading(false);
        // Flip the snapshot sentinel after models load. This causes the dirty
        // tracker to re-snapshot WITH the freshly applied default model, so a
        // newly opened editor doesn't immediately appear dirty.
        setModelDefaultsApplied(true);
      }
    }
    fetchModels();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []); // Run once on mount - agent prop is stable

  // Fetch available teams for team visibility sharing
  React.useEffect(() => {
    async function fetchTeams() {
      try {
        const response = await fetch("/api/dynamic-agents/teams");
        const data = await response.json();
        if (data.success && Array.isArray(data.data)) {
          setAvailableTeams(data.data);
        }
      } catch (err) {
        console.error("Failed to fetch teams:", err);
      }
    }
    fetchTeams();
  }, []);

  // When editing an existing agent, find out if it is the platform default.
  // If it is, lock the visibility selector so the admin can't accidentally
  // demote `global → team` from here — the BFF would reject the request
  // with 409 / AGENT_IS_PLATFORM_DEFAULT anyway, so we surface that
  // constraint up front. The platform-config endpoint is readable by any
  // signed-in user (it's how the Slack bot resolves the DM default), so
  // this works for editors who aren't admins too.
  const [isPlatformDefault, setIsPlatformDefault] = React.useState(false);
  React.useEffect(() => {
    if (!agent?._id) return;
    let cancelled = false;
    async function checkDefault() {
      try {
        const response = await fetch("/api/admin/platform-config");
        const data = await response.json();
        if (cancelled) return;
        if (data.success && data.data?.default_agent_id === agent?._id) {
          setIsPlatformDefault(true);
        }
      } catch {
        // Non-fatal: the BFF will still enforce the invariant on save.
      }
    }
    checkDefault();
    return () => {
      cancelled = true;
    };
  }, [agent?._id]);

  // Step wizard state
  const [requestedStep, setActiveStep] = React.useState<StepId>(initialStep);
  const needsOwnerTeam = !isEditing && visibility === "team" && !ownerTeamSlug.trim();
  const activeStep = !needsOwnerTeam && steps.some((step) => step.id === requestedStep)
    ? requestedStep
    : "basic";

  React.useEffect(() => {
    setActiveStep(initialStep);
  }, [initialStep]);

  const selectStep = React.useCallback(
    (step: StepId) => {
      if (needsOwnerTeam && step !== "basic") {
        setActiveStep("basic");
        setError("Select an owner team before continuing.");
        document.getElementById("ownerTeam")?.focus();
        return;
      }

      setError((previous) =>
        previous === "Select an owner team before continuing." ? null : previous,
      );
      setActiveStep(step);
      onStepChange?.(step);
    },
    [needsOwnerTeam, onStepChange],
  );

  React.useEffect(() => {
    // Reject deep links just like clicks, so selecting an owner later does not
    // resume a step request that was blocked while the owner was missing.
    if (needsOwnerTeam && requestedStep !== "basic") selectStep(requestedStep);
  }, [needsOwnerTeam, requestedStep, selectStep]);

  // Local state for the in-app "you have unsaved changes" confirmation when the
  // user clicks the back arrow. We don't route this through the global store's
  // pendingNavigationHref because closing the editor isn't an href navigation —
  // it's a parent-state flip controlled by the onCancel prop.
  const [pendingClose, setPendingClose] = React.useState(false);

  // Snapshot sentinel: flips once after the async models endpoint resolves and
  // applies a default modelId/modelProvider to a previously empty form. This
  // ensures the dirty-tracking snapshot is taken AFTER defaults are applied,
  // preventing a false "dirty" right after model defaults populate.
  const [modelDefaultsApplied, setModelDefaultsApplied] = React.useState(
    !!source?.model?.id,
  );

  // Aggregate all editable form fields into a single object so the
  // dirty-tracking hook can compare current vs initial values.
  const currentFormValues = React.useMemo(
    () => ({
      name,
      description,
      systemPrompt,
      visibility,
      sharedWithTeams,
      ownerTeamSlug,
      allowedTools,
      allowedRemoteAgents,
      remoteAgentTimeouts,
      builtinTools,
      subagents,
      skills,
      datasourceIds,
      ragCollectionIds,
      features,
      modelId,
      modelProvider,
      reasoningEffort,
      modelStreaming,
      gradientTheme,
      harnessId,
      harnessProfileId,
      harnessOptions,
    }),
    [
      name,
      description,
      systemPrompt,
      visibility,
      sharedWithTeams,
      ownerTeamSlug,
      allowedTools,
      allowedRemoteAgents,
      remoteAgentTimeouts,
      builtinTools,
      subagents,
      skills,
      datasourceIds,
      ragCollectionIds,
      features,
      modelId,
      modelProvider,
      reasoningEffort,
      modelStreaming,
      gradientTheme,
      harnessId,
      harnessProfileId,
      harnessOptions,
    ],
  );

  // The snapshot key combines the source identity with the model-defaults
  // sentinel. When either changes, the dirty hook re-snapshots so the form
  // appears clean.
  const snapshotIdentity = agent?._id ?? cloneFrom?._id ?? "new";
  const snapshotKey = `${snapshotIdentity}|${modelDefaultsApplied ? "1" : "0"}`;

  const { dirty, resetSnapshot } = useEditorDirtyTracking({
    enabled: !readOnly,
    currentValues: currentFormValues,
    snapshotKey,
  });
  const currentStepIndex = steps.findIndex((s) => s.id === activeStep);
  const currentStepConfig = steps.find((s) => s.id === activeStep);

  const goToPreviousStep = () => {
    if (currentStepIndex > 0) {
      selectStep(steps[currentStepIndex - 1].id);
    }
  };

  const goToNextStep = async () => {
    // Gate the instructions → tools transition behind a passing AI Review when
    // the admin has flagged this target as "blocking". `ensurePassedOrRun` is a
    // no-op when the config is disabled or informational.
    if (activeStep === "instructions" && review.isBlocking) {
      const { passed, result } = await review.ensurePassedOrRun();
      if (!passed) {
        const message = buildBlockingMessage(
          review.config,
          result,
          "the comments below",
          "continuing",
        );
        // The banner below carries the message — visible even when the footer
        // is below the fold.
        setBlockingMessage(message);
        return;
      }
      // Review passed — clear any stale blocking banner from a prior attempt.
      setBlockingMessage(null);
    }
    if (currentStepIndex < steps.length - 1) {
      selectStep(steps[currentStepIndex + 1].id);
    }
  };

  /**
   * Call the AI suggest endpoint for a given field.
   * Accepts an optional instruction string for guidance.
   */
  const handleSuggest = async (
    field: "description" | "system_prompt" | "theme",
    instruction?: string,
  ) => {
    if (!name.trim() || !modelId) return;

    setGeneratingField(field);
    try {
      const response = await fetch("/api/dynamic-agents/assistant/suggest", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          field,
          context: {
            name,
            description: description || undefined,
            system_prompt: systemPrompt || undefined,
            allowed_tools:
              supportsTools && Object.keys(allowedTools).length > 0 ? allowedTools : undefined,
            builtin_tools: supportsBuiltinTools ? builtinTools : undefined,
            subagents:
              supportsDelegation && subagents.length > 0
                ? subagents.map((s) => ({
                    agent_id: s.agent_id,
                    name: s.name || s.agent_id,
                    description: s.description,
                  }))
                : undefined,
          },
          model: { id: modelId, provider: modelProvider, reasoning_effort: reasoningEffort },
          ...(instruction ? { instruction } : {}),
          ...(field === "system_prompt" ? { prompt_style: promptStyle } : {}),
        }),
      });

      const data = await response.json();
      if (!data.success) {
        throw new Error(data.error || "Failed to generate suggestion");
      }

      const content = (data.data?.content ?? "").trim();
      if (!content) {
        throw new Error("Empty response from AI");
      }

      switch (field) {
        case "description":
          setDescription(content);
          break;
        case "system_prompt":
          setSystemPrompt(content);
          // Switch to preview tab so user sees the rendered result
          setPromptTab("preview");
          break;
        case "theme": {
          // Check for custom theme response: "custom:#hex1,#hex2,#hex3"
          const customMatch = content.match(
            /custom:\s*(#[0-9a-fA-F]{3,8})\s*,\s*(#[0-9a-fA-F]{3,8})\s*,\s*(#[0-9a-fA-F]{3,8})/,
          );
          if (customMatch) {
            setGradientTheme("custom");
            setCustomThemeConfig({
              gradient_from: customMatch[1],
              gradient_to: customMatch[2],
              accent_color: customMatch[3],
            });
            setShowCustomPicker(true);
            break;
          }
          // Try exact match first (after normalization)
          const normalized = content.toLowerCase().replace(/[^a-z_]/g, "");
          const exactMatch = gradientThemes.find((t) => t.id === normalized);
          if (exactMatch) {
            setGradientTheme(exactMatch.id);
          } else {
            // Fuzzy: find any valid theme ID contained in the response
            const fuzzyMatch = gradientThemes.find((t) =>
              content.toLowerCase().includes(t.id),
            );
            if (fuzzyMatch) {
              setGradientTheme(fuzzyMatch.id);
            } else {
              console.warn(`AI suggested unknown theme "${content}", ignoring`);
              toast("Could not determine theme from AI response", "error");
            }
          }
          break;
        }
      }
    } catch (err) {
      console.error(`AI suggest (${field}) failed:`, err);
      toast(
        getErrorMessage(err, "") || "Failed to generate suggestion",
        "error",
      );
    } finally {
      setGeneratingField(null);
    }
  };

  /**
   * Combined handler for Step 1: generates both description and theme in parallel.
   */
  const handleSuggestBasicInfo = async (instruction?: string) => {
    if (!name.trim() || !modelId) return;
    setShowSuggestBasicInput(false);
    setSuggestBasicInstruction("");
    // If enhancing existing description, pass it as context
    const existingHint =
      enhanceExistingBasic && description.trim()
        ? `The current description is: "${description}". Use it as a starting point and enhance/refine it.`
        : undefined;
    const fullInstruction = [existingHint, instruction]
      .filter(Boolean)
      .join("\n\n");
    setEnhanceExistingBasic(false);
    // Run both in parallel
    await Promise.all([
      handleSuggest("description", fullInstruction || undefined),
      handleSuggest("theme", instruction),
    ]);
  };

  /**
   * Handle system prompt suggestion via the popover.
   * If enhanceExisting is checked and content exists, pass it as context.
   */
  const handleSuggestSystemPrompt = (instruction?: string) => {
    setShowSuggestPromptInput(false);
    setSuggestPromptInstruction("");
    // If user wants to enhance existing content, pass it as context
    const existingHint =
      enhanceExisting && systemPrompt.trim()
        ? `The current system prompt is provided below — use it as a starting point and enhance/refine it based on the user's guidance.\n\n<current_prompt>\n${systemPrompt}\n</current_prompt>`
        : undefined;
    const fullInstruction = [existingHint, instruction]
      .filter(Boolean)
      .join("\n\n");
    setEnhanceExisting(false);
    handleSuggest("system_prompt", fullInstruction || undefined);
  };

  const canSuggest = name.trim() && modelId && !generatingField;
  const isGenerating = !!generatingField;

  const handleSubmit = async (
    e?: React.FormEvent,
    opts?: { forceConfirmNotMember?: boolean },
  ) => {
    e?.preventDefault();
    setLoading(true);
    setError(null);
    setTransferNeedsServerConfirm(false);
    // `setState` is async, so a confirm-and-retry can't rely on the freshly-set
    // `transferConfirmedNotMember`; the caller passes the value through opts.
    const confirmNotMember =
      opts?.forceConfirmNotMember || transferConfirmedNotMember;

    // Gate save behind a passing AI Review when the admin has flagged this
    // target as "blocking". `ensurePassedOrRun` is a no-op when the config is
    // disabled or informational.
    // Capture the freshly-run review result so the grade we persist below
    // comes from the run we just awaited — `review.result` state lags by a
    // render after an inline `ensurePassedOrRun`, so reading it here would
    // stamp a stale (often null) grade onto the save.
    let reviewResult = review.result;
    if (review.isBlocking) {
      const { passed, result } = await review.ensurePassedOrRun();
      reviewResult = result;
      if (!passed) {
        const message = buildBlockingMessage(
          review.config,
          result,
          "the Instructions step",
          "saving",
        );
        // Return to the reviewed content so the user can see and act on the
        // inline comments; the banner below carries the message.
        selectStep("instructions");
        setBlockingMessage(message);
        setLoading(false);
        return;
      }
    }

    // Validate required fields
    if (supportsConfiguredModel && (!modelId || !modelProvider)) {
      setError("Model selection is required");
      setLoading(false);
      return;
    }
    if (!isEditing && visibility === "team" && !ownerTeamSlug) {
      setError("Owner team is required");
      setLoading(false);
      return;
    }

    // Validate ID for new agents
    if (!isEditing) {
      if (!generatedId) {
        setError("Agent name is required to generate ID");
        setLoading(false);
        return;
      }
      if (idClash) {
        setError(
          `Agent ID "${generatedId}" already exists. Please use a different name.`,
        );
        setLoading(false);
        return;
      }
    }

    try {
      if (builderSchemaError) throw new Error(builderSchemaError);
      if (requiresHarnessConfirmation && !harnessSwitchConfirmed) throw new Error("Confirm that the new harness applies to new conversations");
      // Build UI config if gradient theme is set
      const uiConfig: AgentUIConfig | undefined = gradientTheme
        ? {
            gradient_theme: gradientTheme,
            ...(gradientTheme === "custom"
              ? { custom_theme_config: customThemeConfig }
              : {}),
          }
        : undefined;

      // Stamp the latest in-memory review verdict onto the saved row so the
      // list view can show a grade badge without re-running the LLM. Only
      // emit the field when we actually have a result this session — never
      // overwrite a prior `last_review` with null.
      const lastReview = buildLastReview(reviewResult, "agent-system-prompt");

      // Restricted: send the real (possibly empty) arrays. Unrestricted with
      // nothing to clear: omit both fields entirely. Unrestricted after
      // having been restricted: explicitly clear both back to unset.
      const knowledgeFields = !supportsKnowledge ? {} : restrictKnowledge
        ? { datasource_ids: datasourceIds, rag_collection_ids: ragCollectionIds }
        : hadExplicitKnowledgeScope
          ? { datasource_ids: null, rag_collection_ids: null }
          : {};

      if (harnessId !== "dynamic_agents" && !harnessProfileId) {
        throw new Error("Select an operator profile for the execution harness before saving");
      }
      if (!isDeepAgents && (harnessLoading || harnessLoadError ||
        selectedHarness?.availability !== "available" || selectedHarness.certification === "blocked")) {
        throw new Error(harnessLoadError || "The selected harness is unavailable");
      }

      // Keep saved Deep Agents settings intact while another harness is active.
      // Only supported settings are published into its execution blueprint.
      const runtimeFields = {
        ...(supportsTools && { allowed_tools: allowedTools }),
        ...(supportsRemoteAgents && { allowed_remote_agents: allowedRemoteAgents,
          remote_agent_timeouts: Object.fromEntries(Object.entries(remoteAgentTimeouts).filter(([id]) => allowedRemoteAgents.includes(id))) }),
        ...(supportsBuiltinTools && { builtin_tools: builtinTools }),
        ...(supportsDelegation && { subagents: subagents.length > 0 ? subagents : undefined }),
        ...(supportsSkills && { skills }),
        ...knowledgeFields,
        ...(supportsConfiguredModel && { model: { id: modelId, provider: modelProvider, reasoning_effort: reasoningEffort, streaming: modelStreaming } }),
        ...(supportsMiddleware && { features }),
        ...(supportsApprovals && { interrupt_on: interruptOn }),
      };

      const targetAgentId = isEditing ? agent._id : generatedId;
      let preparedHarnessSave: {
        blueprint: AgentBlueprint;
        catalog_revision: string;
        config_fingerprint: string;
      } | null = null;

      // Validate the independent blueprint before mutating the legacy agent
      // document. This keeps an invalid provider configuration from changing
      // an existing Dynamic Agent or creating a half-configured new one.
      if (harnessId !== "dynamic_agents") {
        const toolBindings = Object.entries(supportsTools ? allowedTools : {}).flatMap(
          ([serverId, tools]) => {
            if (tools === true) {
              throw new Error("Select individual tools for this harness instead of all tools on a server");
            }
            return Array.isArray(tools)
              ? tools.map((toolName) => ({ tool_id: `${serverId}.${toolName}` }))
              : [];
          },
        );
        const blueprint: AgentBlueprint = {
          id: targetAgentId,
          name,
          description,
          harness: {
            id: harnessId,
            profile_id: harnessProfileId,
            options: harnessOptions,
          },
          prompt: { system: systemPrompt, variables: {}, context_sources: [] },
          model: supportsConfiguredModel ? { policy: "configured", id: modelId, provider: modelProvider } : { policy: "harness_default" },
          tools: { bindings: toolBindings, approval_policy: supportsTools && !supportsApprovals ? "never" : "sensitive_only" },
          thread: { persistence: "durable" },
          memory: { enabled: false },
          workspace: { persistence: "none" },
          streaming: { protocol: "canonical", replay: "required" },
          delegation: {
            agents: supportsDelegation ? subagents.map(child => child.agent_id) : [],
            max_depth: supportsDelegation ? 1 : 0,
            max_parallel: 1,
          },
        };
        const validationResponse = await fetch(
          "/api/harness-engine/agent-drafts/validate",
          {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({
              blueprint,
              catalog_revision: catalogRevision,
            }),
          },
        );
        const validation = await validationResponse.json();
        if (!validationResponse.ok || !validation.data?.valid) {
          const reason = validation.data?.issues
            ?.map((issue: { message: string }) => issue.message)
            .join("; ");
          throw new Error(
            `Harness blueprint is invalid; the agent was not saved: ${reason || validation.error || "unknown error"}`,
          );
        }
        preparedHarnessSave = {
          blueprint: validation.data.normalized_blueprint,
          catalog_revision: validation.data.catalog_revision,
          config_fingerprint: validation.data.config_fingerprint,
        };
      }

      if (isEditing) {
        // Update existing agent
        const updateData: DynamicAgentConfigUpdate & {
          owner_team_slug?: string;
          confirm_not_member?: boolean;
        } = {
          execution_harness_id: harnessId,
          ...(preparedHarnessSave && { harness_save: { ...preparedHarnessSave, expected_revision: harnessRevision } }),
          name,
          description: description || undefined,
          system_prompt: systemPrompt,
          visibility,
          shared_with_teams:
            visibility === "team" ? sharedWithTeams : undefined,
          ...runtimeFields,
          ui: uiConfig,
          // Ownership transfer (US3): only send owner_team_slug when the user
          // changed the owner picker, so a normal edit never trips the route's
          // transfer guard.
          ...(transferRequested
            ? {
                owner_team_slug: ownerTeamSlug,
                confirm_not_member: confirmNotMember,
              }
            : {}),
          ...(lastReview ? { last_review: lastReview } : {}),
        };

        const response = await fetch(`/api/dynamic-agents?id=${agent._id}`, {
          method: "PUT",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify(updateData),
        });

        const data = await response.json();
        if (!data.success) {
          // The destination team is one the caller can pick in the UI but is
          // not an OpenFGA member of (e.g. org admin). Offer an explicit
          // confirm-and-retry instead of a dead-end error.
          if (data.code === "TRANSFER_NOT_MEMBER_UNCONFIRMED") {
            const err = new Error(data.error || "Confirmation required");
            (err as { code?: string }).code = "TRANSFER_NOT_MEMBER_UNCONFIRMED";
            throw err;
          }
          throw new Error(data.error || "Failed to update agent");
        }
      } else {
        // Create new agent
        const createData: DynamicAgentConfigCreate = {
          id: generatedId,
          execution_harness_id: harnessId,
          ...(preparedHarnessSave && { harness_save: { ...preparedHarnessSave, expected_revision: harnessRevision } }),
          name,
          description: description || undefined,
          system_prompt: systemPrompt,
          visibility,
          owner_team_slug: ownerTeamSlug,
          shared_with_teams:
            visibility === "team" ? sharedWithTeams : undefined,
          ...runtimeFields,
          ui: uiConfig,
          ...(lastReview ? { last_review: lastReview } : {}),
        };

        const response = await fetch("/api/dynamic-agents", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify(createData),
        });

        const data = await response.json();
        if (!data.success) {
          throw new Error(data.error || "Failed to create agent");
        }
      }

      // Clear unsaved-changes state BEFORE calling onSave(): the parent will
      // unmount this editor in response to onSave, and we want the global flag
      // to be false by the time any header/tab guards re-evaluate. resetSnapshot
      // also re-points the snapshot at the just-saved values so a follow-up
      // dirty check (in the unlikely case the editor stays mounted) is correct.
      resetSnapshot();
      useUnsavedChangesStore.getState().setUnsaved(false);

      onSave();
    } catch (err) {
      if (
        typeof err === "object" &&
        err !== null &&
        "code" in err &&
        err.code === "TRANSFER_NOT_MEMBER_UNCONFIRMED"
      ) {
        setTransferNeedsServerConfirm(true);
        setError(
          'You are not a member of the destination team. Click "Confirm Transfer" to transfer ownership anyway.',
        );
      } else {
        setError(getErrorMessage(err, "") || "An error occurred");
      }
    } finally {
      setLoading(false);
    }
  };

  // Each entry describes one reason the Create Agent / Save Changes button is
  // disabled. We render `blockers[0]` next to the button so the user always
  // sees WHY they can't submit and on which step to fix it — previously the
  // button just went `disabled` with no explanation, which the user reported
  // as confusing (especially the Owner Team case, where the picker sits on
  // the first wizard step but the button lives below step 5's content).
  //
  // assisted-by Cursor claude-opus-4-7
  const ownerTeamMissing =
    !isEditing && visibility === "team" && !ownerTeamSlug;

  const blockers: { field: string; label: string; step: StepId }[] =
    React.useMemo(() => {
      const list: { field: string; label: string; step: StepId }[] = [];
      if (!name.trim()) {
        list.push({ field: "name", label: "Agent name", step: "basic" });
      }
      if (supportsConfiguredModel && availableModels.length === 0) {
        // Distinct from "model not picked" — the user can't pick anything
        // because nothing is configured. Surfacing this separately tells the
        // operator the problem is upstream (no providers configured).
        list.push({
          field: "modelAvailability",
          label: "At least one model provider must be configured",
          step: "basic",
        });
      } else if (supportsConfiguredModel && (!modelId || !modelProvider)) {
        list.push({ field: "model", label: "Model", step: "basic" });
      }
      if (!isDeepAgents) {
        if (harnessLoading) {
          list.push({ field: "harnessLoading", label: "Load the harness configuration", step: "basic" });
        } else if (harnessLoadError || selectedHarness?.availability !== "available" || selectedHarness.certification === "blocked") {
          list.push({ field: "harnessAvailability", label: "An available execution harness", step: "basic" });
        } else if (!selectedHarness.profiles.some((profile) => profile.id === harnessProfileId && profile.available)) {
          list.push({ field: "harnessProfile", label: "An available operator profile", step: "basic" });
        }
      }
      if (ownerTeamMissing) {
        list.push({ field: "ownerTeam", label: "Owner Team", step: "basic" });
      }
      if (!systemPrompt.trim()) {
        list.push({
          field: "systemPrompt",
          label: "Instructions (system prompt)",
          step: "instructions",
        });
      }
      // Restrict was just turned on (it wasn't already an explicit scope
      // when this editor opened) and nothing has been picked yet - saving
      // now would send explicit empty arrays, a deliberate opt-out that
      // disables this agent's RAG tools entirely, not "unrestricted"
      // (that's what leaving Restrict off does). Block the save instead of
      // silently locking the agent out of every datasource. An agent that
      // already had an explicit scope may still be intentionally cleared
      // to opt out - only gate the "just enabled, nothing picked" case.
      if (
        supportsKnowledge && restrictKnowledge &&
        !hadExplicitKnowledgeScope &&
        datasourceIds.length === 0 &&
        ragCollectionIds.length === 0
      ) {
        list.push({
          field: "knowledgeScope",
          label: "Pick at least one collection or datasource, or turn off Restrict",
          step: "knowledge",
        });
      }
      return list;
    }, [
      name,
      systemPrompt,
      modelId,
      modelProvider,
      availableModels.length,
      isDeepAgents,
      supportsConfiguredModel,
      supportsKnowledge,
      harnessLoading,
      harnessLoadError,
      selectedHarness,
      harnessProfileId,
      ownerTeamMissing,
      restrictKnowledge,
      hadExplicitKnowledgeScope,
      datasourceIds,
      ragCollectionIds,
    ]);

  const isValid = blockers.length === 0;
  const firstBlocker = blockers[0];
  const blockerStepLabel = firstBlocker
    ? (steps.find((s) => s.id === firstBlocker.step)?.label ??
      firstBlocker.step)
    : null;

  // Back-button click handler. When the form has unsaved changes, we surface
  // an in-app confirmation modal instead of silently discarding work. The
  // dialog itself is rendered at the bottom of this component.
  const handleBackClick = () => {
    if (dirty) {
      setPendingClose(true);
    } else {
      onCancel();
    }
  };

  const handleConfirmDiscard = () => {
    setPendingClose(false);
    // Belt-and-suspenders: clear the global flag here AND let the unmount
    // cleanup in useEditorDirtyTracking do the same. Either alone is enough.
    useUnsavedChangesStore.getState().setUnsaved(false);
    onCancel();
  };

  const handleCancelDiscard = () => {
    setPendingClose(false);
  };

  return (
    <Card>
      <CardHeader className="pb-2">
        <div className="flex items-start gap-3 sm:gap-4">
          <Button variant="ghost" size="icon" onClick={handleBackClick}>
            <ArrowLeft className="h-4 w-4" />
          </Button>
          <div className="min-w-0 flex-1">
            <CardTitle className="break-words">
              {readOnly
                ? `View Agent - ${agent?.name}`
                : isEditing
                  ? `Edit Agent - ${agent?.name}`
                  : isCloning
                    ? "Clone Agent"
                    : "Create Agent"}
            </CardTitle>
            <CardDescription>
              {readOnly
                ? readOnlyReason === "permissions"
                  ? "You do not have permission to edit this agent"
                  : "This agent is managed by configuration and cannot be edited"
                : isEditing
                  ? "Update the agent configuration"
                  : isCloning
                    ? `Creating a copy of "${cloneFrom?.name}"`
                    : "Configure a new custom AI agent"}
            </CardDescription>
          </div>
          <AgentAvatar
            gradientTheme={gradientTheme}
            customThemeConfig={
              gradientTheme === "custom" ? customThemeConfig : undefined
            }
            rounded="rounded-lg"
            size="ml-auto h-9 w-9 shrink-0"
            iconSize="h-5 w-5"
            className="transition-all"
          />
        </div>
      </CardHeader>
      <CardContent>
        <form onSubmit={handleSubmit} className="space-y-4">
          {/* Step Indicator + title inline */}
          <div className="mt-2 flex flex-col items-stretch gap-3 border-b pb-3 lg:flex-row lg:items-center lg:gap-4">
            <div className="min-w-0 lg:shrink-0">
              <h3 className="break-words text-lg font-bold text-primary sm:text-xl">
                Step {currentStepIndex + 1}: {currentStepConfig?.label}
              </h3>
              <p className="text-xs text-muted-foreground mt-0.5">
                {currentStepConfig?.hint}
              </p>
            </div>
            <StepIndicator
              steps={steps}
              currentStep={activeStep}
              onStepClick={selectStep}
            />
          </div>

          <fieldset
            className={cn("space-y-4 min-w-0", readOnly && "opacity-70")}
          >
            {/* Basic Info Step */}
            {activeStep === "basic" && (
              <div className="space-y-4 pt-2">
                <div className="space-y-2">
                  <div className="p-3 rounded-lg border-2 border-primary/20 bg-primary/5 space-y-3">
                    <div className="flex min-w-0 items-center gap-1">
                      <Select
                        id="harnessId"
                        aria-label="Execution harness"
                        value={harnessId}
                        onChange={(event) => {
                          if (event.target.value === "__add_existing_agentcore__") setConnectionDialogOpen(true);
                          else selectHarness(event.target.value);
                        }}
                        disabled={loading || harnessLoading || !!readOnly}
                        className="flex h-10 min-w-0 flex-1 rounded-md border border-input bg-background px-3 py-2 text-sm font-medium shadow-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
                      >
                        <option value="dynamic_agents">
                          LangChain Deep Agents (default) — {harnessStatusLabel("dynamic_agents", harnessDescriptors.find(item => item.id === "dynamic_agents")?.certification)}
                        </option>
                        {!isDeepAgents && !selectedHarness && (
                          <option value={harnessId}>{harnessId} (unavailable)</option>
                        )}
                        {harnessDescriptors.filter(descriptor => descriptor.id !== "dynamic_agents").map((descriptor) => (
                          <option
                            key={descriptor.id}
                            value={descriptor.id}
                            disabled={
                              descriptor.availability !== "available" ||
                              descriptor.certification === "blocked"
                            }
                          >
                            {harnessDisplayName(descriptor)} — {harnessStatusLabel(descriptor.id, descriptor.certification)}
                          </option>
                        ))}
                        {connectionConfigured !== null && (
                          <option value="__add_existing_agentcore__" disabled={!connectionConfigured}>
                            Add existing AgentCore agent{connectionConfigured ? "…" : " (Harness Engine not configured)"}
                          </option>
                        )}
                      </Select>
                      <HarnessHelp harnessId={harnessId} descriptors={harnessDescriptors} />
                    </div>
                    {selectedHarness && (
                      <div className="space-y-2">
                        <div className="flex items-center gap-1.5">
                          <Label htmlFor="harnessProfile">Operator profile</Label>
                          <TooltipProvider delayDuration={0}>
                            <Tooltip>
                              <TooltipTrigger asChild>
                                <button
                                  type="button"
                                  aria-label="About operator profiles"
                                  className="rounded p-0.5 text-muted-foreground/60 transition-colors hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
                                >
                                  <Info className="h-3.5 w-3.5" aria-hidden="true" />
                                </button>
                              </TooltipTrigger>
                              <TooltipContent
                                side="top"
                                sideOffset={6}
                                className="max-w-xs whitespace-normal px-3 py-2 text-left text-xs font-normal leading-relaxed"
                              >
                                An operator profile is an administrator-managed execution
                                configuration. It selects approved provider resources,
                                security settings, and limits without exposing credentials or
                                infrastructure identifiers to agent authors.
                              </TooltipContent>
                            </Tooltip>
                          </TooltipProvider>
                        </div>
                        <Select
                          id="harnessProfile"
                          value={harnessProfileId}
                          onChange={(event) =>
                            setHarnessProfileId(event.target.value)
                          }
                          disabled={loading || !!readOnly}
                          className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
                        >
                          <option value="" disabled>Select an operator profile</option>
                          {harnessProfileId && !selectedHarness.profiles.some((profile) => profile.id === harnessProfileId) && (
                            <option value={harnessProfileId} disabled>{harnessProfileId} (unavailable)</option>
                          )}
                          {selectedHarness.profiles.map((profile) => (
                            <option
                              key={profile.id}
                              value={profile.id}
                              disabled={!profile.available}
                            >
                              {profile.display_name}
                            </option>
                          ))}
                        </Select>
                        <p className="text-xs text-muted-foreground">
                          Profiles are managed by your administrator and define
                          the execution model, security settings, and limits.
                        </p>
                      </div>
                    )}
                    {["claude_code_acp", "claude_code_cli_acp"].includes(harnessId) && selectedHarness ? (
                      <ClaudeACPSettings descriptor={selectedHarness} profileId={harnessProfileId} />
                    ) : isDeepAgents ? (
                      <p className="text-xs text-muted-foreground">
                        Configure tools, knowledge, and skills for LangChain Deep
                        Agents. Subagents, approvals, and middleware are under Advanced.
                      </p>
                    ) : (
                      <div className="space-y-1 text-xs text-muted-foreground">
                        <p>The operator profile selects the execution model and runtime settings.</p>
                        {!supportsTools && <p>Tool configuration is unavailable for this harness.</p>}
                        <p>Knowledge, skills, subagents, approval rules, and middleware are not configurable for this harness.</p>
                        {isEditing && <p>Saved Deep Agents settings are retained and apply when Deep Agents is selected.</p>}
                      </div>
                    )}

                    {requiresHarnessConfirmation && <label className="flex items-start gap-2 text-sm">
                      <input type="checkbox" checked={harnessSwitchConfirmed} onChange={event => setHarnessSwitchConfirmed(event.target.checked)} />
                      I understand that existing conversations keep their saved runtime and configuration; new conversations use this harness.
                    </label>}
                    {!isDeepAgents && harnessLoadError && (
                      <p role="alert" className="text-xs text-destructive">{harnessLoadError}. Reopen the editor to retry.</p>
                    )}
                  </div>
                </div>
                <div className="space-y-2">
                  <Label htmlFor="name">
                    Agent Name <span className="text-destructive">*</span>
                  </Label>
                  <Input
                    id="name"
                    placeholder="e.g., Code Review Agent"
                    value={name}
                    onChange={(e) => setName(e.target.value)}
                    disabled={loading || !!readOnly}
                  />
                  {/* Show generated ID */}
                  {isEditing ? (
                    <p className="text-xs text-muted-foreground">
                      id:{" "}
                      <code className="bg-muted px-1 py-0.5 rounded">
                        {agent._id}
                      </code>
                    </p>
                  ) : generatedId ? (
                    <p
                      className={`text-xs ${idClash ? "text-destructive" : "text-muted-foreground"}`}
                    >
                      id:{" "}
                      <code
                        className={`px-1 py-0.5 rounded ${idClash ? "bg-destructive/10" : "bg-muted"}`}
                      >
                        {generatedId}
                      </code>
                      {idClash && (
                        <span className="ml-1 font-medium">
                          - already exists, choose a different name
                        </span>
                      )}
                    </p>
                  ) : null}
                </div>

                {/* LLM Model - right after name so AI Suggest buttons can use it */}
                {supportsConfiguredModel ? (
                  <>
                    <div className="space-y-2">
                      <Label htmlFor="modelId">
                        LLM Model <span className="text-destructive">*</span>
                      </Label>
                      <div className="p-3 rounded-lg border-2 border-primary/20 bg-primary/5">
                        <ModelPicker
                          id="modelId"
                          options={availableModels}
                          modelId={modelId}
                          modelProvider={modelProvider}
                          onChange={(selectedId, selectedProvider) => {
                            setModelId(selectedId);
                            setModelProvider(selectedProvider);
                          }}
                          loading={modelsLoading}
                          disabled={
                            loading ||
                            !!readOnly
                          }
                          triggerClassName="font-medium"
                        />
                        {!modelsLoading && availableModels.length === 0 ? (
                          <p className="text-xs text-destructive mt-2">
                            No LLM models available. Please check your deployment
                            configuration.
                          </p>
                        ) : (
                          <p className="text-xs text-muted-foreground mt-2">
                            The language model that powers this agent&apos;s
                            reasoning.
                          </p>
                        )}
                      </div>
                    </div>

                    <div className="space-y-2">
                      <Label htmlFor="reasoningEffort">Default reasoning effort</Label>
                      <Select
                        id="reasoningEffort"
                        value={reasoningEffort}
                        onChange={(event) => setReasoningEffort(event.target.value as ReasoningEffort)}
                        disabled={loading || !!readOnly || supportedReasoningEfforts === null || supportedReasoningEfforts.length === 0}
                      >
                        {(["low", "medium", "high", "max"] as ReasoningEffort[]).map((effort) => (
                          <option
                            key={effort}
                            value={effort}
                            disabled={supportedReasoningEfforts !== null && !supportedReasoningEfforts.includes(effort)}
                          >
                            {effort}
                          </option>
                        ))}
                      </Select>
                      {supportedReasoningEfforts !== null && supportedReasoningEfforts.length === 0 ? (
                        <p className="text-xs text-amber-600 dark:text-amber-400">
                          This model does not advertise configurable reasoning. The saved value is ignored, and the provider uses its own behavior.
                        </p>
                      ) : supportedReasoningEfforts !== null ? (
                        <p className="text-xs text-muted-foreground">
                          New conversations start at this level. Each chat can override it.
                        </p>
                      ) : null}
                    </div>

                    {isDeepAgents && (
                      <div className="space-y-2">
                        <Label htmlFor="modelStreaming">Model response streaming</Label>
                        <Select
                          id="modelStreaming"
                          value={modelStreaming === null ? "default" : String(modelStreaming)}
                          onChange={(event) => setModelStreaming(
                            event.target.value === "default" ? null : event.target.value === "true",
                          )}
                          disabled={loading || !!readOnly}
                          aria-describedby="modelStreaming-help"
                        >
                          <option value="default">Use deployment default</option>
                          <option value="true">Enabled</option>
                          <option value="false">Disabled</option>
                        </Select>
                        <p id="modelStreaming-help" className="text-xs text-muted-foreground">
                          Disable if your model provider or proxy does not support streaming.
                          Saved changes apply to the next chat turn. Responses already running are unchanged.
                        </p>
                      </div>
                    )}
                  </>
                ) : (
                  <CollapsibleSection
                    title="AI writing assistant"
                    description="Optional model for AI Suggest when writing agent details and instructions"
                  >
                    <Label htmlFor="authoringModel">Writing assistant model</Label>
                    <ModelPicker
                      id="authoringModel"
                      options={availableModels}
                      modelId={modelId}
                      modelProvider={modelProvider}
                      onChange={(selectedId, selectedProvider) => {
                        setModelId(selectedId);
                        setModelProvider(selectedProvider);
                      }}
                      loading={modelsLoading}
                      disabled={loading || !!readOnly}
                    />
                    <p className="mt-2 text-xs text-muted-foreground">
                      Used only by AI Suggest. The agent runs with the model configured by its operator profile.
                    </p>
                  </CollapsibleSection>
                )}

                <div className="space-y-2">
                  <div className="flex items-center justify-between relative">
                    <Label htmlFor="description">Description</Label>
                    <div className="relative">
                      <Button
                        type="button"
                        variant="outline"
                        size="sm"
                        className="h-7 text-xs gap-1 px-2 border-primary/30 text-primary hover:bg-primary/10"
                        disabled={!canSuggest || loading || !!readOnly}
                        onClick={() => {
                          setShowSuggestBasicInput((v) => {
                            if (!v)
                              setEnhanceExistingBasic(!!description.trim());
                            return !v;
                          });
                          setShowSuggestPromptInput(false);
                        }}
                        title={
                          !name.trim()
                            ? "Enter a name first"
                            : !modelId
                              ? "Select a model first"
                              : "AI-generate description and theme"
                        }
                      >
                        {isGenerating &&
                        (generatingField === "description" ||
                          generatingField === "theme") ? (
                          <Loader2 className="h-3 w-3 animate-spin" />
                        ) : (
                          <Sparkles className="h-3 w-3" />
                        )}
                        AI Suggest
                      </Button>
                      <AnimatePresence>
                        {showSuggestBasicInput && (
                          <motion.div
                            initial={{ opacity: 0, y: -4, scale: 0.95 }}
                            animate={{ opacity: 1, y: 0, scale: 1 }}
                            exit={{ opacity: 0, y: -4, scale: 0.95 }}
                            transition={{ duration: 0.15 }}
                            className="absolute top-full right-0 mt-1 z-50 w-80 p-3 rounded-lg border border-border/50 bg-background shadow-xl"
                          >
                            <label className="text-xs font-medium text-muted-foreground mb-1.5 block">
                              Describe what this agent does
                            </label>
                            <p className="text-[11px] text-muted-foreground/70 mb-2">
                              Generates a description and picks a matching
                              theme.
                            </p>
                            <Input
                              autoFocus
                              value={suggestBasicInstruction}
                              onChange={(e) =>
                                setSuggestBasicInstruction(e.target.value)
                              }
                              onKeyDown={(e) => {
                                if (e.key === "Enter")
                                  handleSuggestBasicInfo(
                                    suggestBasicInstruction.trim() || undefined,
                                  );
                                if (e.key === "Escape")
                                  setShowSuggestBasicInput(false);
                              }}
                              placeholder="e.g., Summarizes documents and answers questions..."
                              className="h-8 text-sm mb-2"
                            />
                            {description.trim() && (
                              <label className="flex items-center gap-2 mb-2 cursor-pointer">
                                <input
                                  type="checkbox"
                                  checked={enhanceExistingBasic}
                                  onChange={(e) =>
                                    setEnhanceExistingBasic(e.target.checked)
                                  }
                                  className="rounded border-muted"
                                />
                                <span className="text-xs text-muted-foreground">
                                  Enhance existing text
                                </span>
                              </label>
                            )}
                            <div className="flex justify-end gap-1.5">
                              <Button
                                type="button"
                                variant="ghost"
                                size="sm"
                                className="h-7 text-xs"
                                onClick={() => setShowSuggestBasicInput(false)}
                              >
                                Cancel
                              </Button>
                              <Button
                                type="button"
                                size="sm"
                                className="h-7 text-xs gap-1 gradient-primary text-white"
                                onClick={() =>
                                  handleSuggestBasicInfo(
                                    suggestBasicInstruction.trim() || undefined,
                                  )
                                }
                              >
                                <Sparkles className="h-3 w-3" />
                                Generate
                              </Button>
                            </div>
                          </motion.div>
                        )}
                      </AnimatePresence>
                    </div>
                  </div>
                  <Textarea
                    id="description"
                    placeholder="What does this agent do?"
                    value={description}
                    onChange={(e) => setDescription(e.target.value)}
                    disabled={loading || !!readOnly}
                    rows={2}
                  />
                </div>

                {/* Agent Theme */}
                <div className="space-y-2">
                  <Label>Agent Theme</Label>
                  <p className="text-xs text-muted-foreground mb-2">
                    Choose a color theme for this agent&apos;s avatar.
                  </p>
                  <div className="grid grid-cols-6 gap-1.5">
                    {gradientThemes.map((theme) => (
                      <button
                        key={theme.id}
                        type="button"
                        onClick={() => {
                          setGradientTheme(theme.id);
                          setShowCustomPicker(false);
                        }}
                        className={cn(
                          "flex items-center gap-2 px-2 py-1.5 rounded-lg border transition-all text-left",
                          gradientTheme === theme.id
                            ? "border-primary bg-primary/10"
                            : "border-border hover:border-primary/50 hover:bg-muted/50",
                        )}
                        disabled={loading || !!readOnly}
                        title={theme.description}
                      >
                        <div
                          className="w-6 h-6 rounded-md shrink-0"
                          style={{
                            background: `linear-gradient(to bottom right, ${theme.from}, ${theme.to})`,
                          }}
                        />
                        <div className="flex-1 min-w-0">
                          <span className="text-[11px] font-medium block truncate">
                            {theme.label.split(" (")[0]}
                          </span>
                          <span className="text-[10px] text-muted-foreground block truncate">
                            {theme.description}
                          </span>
                        </div>
                        {gradientTheme === theme.id && (
                          <Check className="h-3 w-3 text-primary shrink-0" />
                        )}
                      </button>
                    ))}
                    {/* Custom theme button */}
                    <div className="relative">
                      <button
                        type="button"
                        onClick={() => {
                          setGradientTheme("custom");
                          setShowCustomPicker(!showCustomPicker);
                        }}
                        className={cn(
                          "flex items-center gap-2 px-2 py-1.5 rounded-lg border transition-all text-left w-full",
                          gradientTheme === "custom"
                            ? "border-primary bg-primary/10"
                            : "border-border hover:border-primary/50 hover:bg-muted/50",
                        )}
                        disabled={loading || !!readOnly}
                        title="Custom colors"
                      >
                        <div
                          className="w-6 h-6 rounded-md shrink-0 border border-dashed border-muted-foreground/40 flex items-center justify-center"
                          style={
                            gradientTheme === "custom"
                              ? {
                                  background: `linear-gradient(to bottom right, ${customThemeConfig.gradient_from}, ${customThemeConfig.gradient_to})`,
                                }
                              : undefined
                          }
                        >
                          {gradientTheme !== "custom" && (
                            <span className="text-[10px] text-muted-foreground">
                              +
                            </span>
                          )}
                        </div>
                        <div className="flex-1 min-w-0">
                          <span className="text-[11px] font-medium block truncate">
                            Custom
                          </span>
                          <span className="text-[10px] text-muted-foreground block truncate">
                            Pick your own
                          </span>
                        </div>
                        {gradientTheme === "custom" && (
                          <Check className="h-3 w-3 text-primary shrink-0" />
                        )}
                      </button>

                      {/* Custom theme picker popup — positioned to the left of the button */}
                      {showCustomPicker && gradientTheme === "custom" && (
                        <div className="absolute right-full top-0 mr-2 p-4 rounded-lg border border-border bg-card shadow-lg space-y-4 w-72 z-50">
                          {/* Preview */}
                          <div className="flex items-center gap-3">
                            <AgentAvatar
                              gradientTheme="custom"
                              customThemeConfig={customThemeConfig}
                              rounded="rounded-xl"
                              size="h-12 w-12"
                              iconSize="h-6 w-6"
                              className="transition-all"
                            />
                            <div className="text-xs text-muted-foreground">
                              Live preview
                            </div>
                          </div>

                          {/* Color inputs */}
                          <div className="space-y-2.5">
                            <div className="flex items-center gap-2">
                              <label className="text-[11px] font-medium w-24 shrink-0">
                                Gradient From
                              </label>
                              <div className="flex items-center gap-1.5 flex-1">
                                <input
                                  type="color"
                                  value={customThemeConfig.gradient_from}
                                  onChange={(e) =>
                                    setCustomThemeConfig((prev) => ({
                                      ...prev,
                                      gradient_from: e.target.value,
                                    }))
                                  }
                                  className="h-7 w-7 rounded cursor-pointer border border-border shrink-0"
                                />
                                <Input
                                  value={customThemeConfig.gradient_from}
                                  onChange={(e) =>
                                    setCustomThemeConfig((prev) => ({
                                      ...prev,
                                      gradient_from: e.target.value,
                                    }))
                                  }
                                  className="h-7 text-xs font-mono"
                                  placeholder="#6366f1"
                                />
                              </div>
                            </div>
                            <div className="flex items-center gap-2">
                              <label className="text-[11px] font-medium w-24 shrink-0">
                                Gradient To
                              </label>
                              <div className="flex items-center gap-1.5 flex-1">
                                <input
                                  type="color"
                                  value={customThemeConfig.gradient_to}
                                  onChange={(e) =>
                                    setCustomThemeConfig((prev) => ({
                                      ...prev,
                                      gradient_to: e.target.value,
                                    }))
                                  }
                                  className="h-7 w-7 rounded cursor-pointer border border-border shrink-0"
                                />
                                <Input
                                  value={customThemeConfig.gradient_to}
                                  onChange={(e) =>
                                    setCustomThemeConfig((prev) => ({
                                      ...prev,
                                      gradient_to: e.target.value,
                                    }))
                                  }
                                  className="h-7 text-xs font-mono"
                                  placeholder="#1e1b4b"
                                />
                              </div>
                            </div>
                            <div className="flex items-center gap-2">
                              <label className="text-[11px] font-medium w-24 shrink-0">
                                Icon Color
                              </label>
                              <div className="flex items-center gap-1.5 flex-1">
                                <input
                                  type="color"
                                  value={customThemeConfig.accent_color}
                                  onChange={(e) =>
                                    setCustomThemeConfig((prev) => ({
                                      ...prev,
                                      accent_color: e.target.value,
                                    }))
                                  }
                                  className="h-7 w-7 rounded cursor-pointer border border-border shrink-0"
                                />
                                <Input
                                  value={customThemeConfig.accent_color}
                                  onChange={(e) =>
                                    setCustomThemeConfig((prev) => ({
                                      ...prev,
                                      accent_color: e.target.value,
                                    }))
                                  }
                                  className="h-7 text-xs font-mono"
                                  placeholder="#ffffff"
                                />
                              </div>
                            </div>
                          </div>

                          {/* Done button */}
                          <Button
                            type="button"
                            size="sm"
                            className="w-full h-7 text-xs"
                            onClick={() => setShowCustomPicker(false)}
                          >
                            Done
                          </Button>
                        </div>
                      )}
                    </div>
                  </div>
                </div>

                {/* Owner-team picker + share-with-teams multi-select +
                  effective-access preview are now the shared
                  <TeamOwnershipFields> control bundle (spec 2026-06-03,
                  US1). The agent's visibility toggle is interleaved via
                  `betweenOwnerAndShare`, the platform-admin warning via
                  `ownerExtra`, and the agent-specific grant copy via
                  `renderGrantDetail`, so the UX is unchanged (SC-006). */}
                <TeamOwnershipFields
                  ownerTeamSlug={ownerTeamSlug}
                  sharedTeamSlugs={sharedWithTeams}
                  isEditing={isEditing}
                  ownerRequired={visibility === "team"}
                  allowTransfer={isEditing}
                  resourceNoun="agent"
                  disabled={loading || !!readOnly}
                  showShare={visibility === "team"}
                  currentUserTeamSlugs={availableTeams
                    .map((team) => team.slug)
                    .filter((slug): slug is string => Boolean(slug))}
                  onOwnerTeamChange={(slug) => {
                    setOwnerTeamSlug(slug);
                    if (slug.trim()) setError(null);
                  }}
                  onSharedTeamsChange={setSharedWithTeams}
                  onTransfer={(_newOwnerSlug, confirmedNotMember) => {
                    // The component already applied the new slug via
                    // onOwnerTeamChange; record that this edit is a transfer so
                    // the PUT sends owner_team_slug + confirm_not_member.
                    setTransferRequested(true);
                    setTransferConfirmedNotMember(confirmedNotMember);
                    // Picking a different destination clears any stale
                    // not-a-member rejection so the inline "Confirm Transfer"
                    // button (and its message) can't linger and refer to the
                    // previously-chosen team.
                    setTransferNeedsServerConfirm(false);
                    setError(null);
                  }}
                  availableTeams={availableTeams
                    .filter((team): team is typeof team & { slug: string } =>
                      Boolean(team.slug),
                    )
                    .map<TeamPickerOption>((team) => ({
                      slug: team.slug,
                      name: team.name,
                      _id: team._id,
                    }))}
                  ownerTeamOptions={availableTeams
                    .filter((team): team is typeof team & { slug: string } =>
                      Boolean(team.slug),
                    )
                    .map<TeamPickerOption>((team) => ({
                      slug: team.slug,
                      name: team.user_role
                        ? `${team.name} (${team.user_role})`
                        : team.name,
                      _id: team._id,
                      disabled: team.can_own_agents === false,
                    }))}
                  ownerHelpText={
                    <>
                      Select a team you belong to as the owner. You manage this
                      agent as its creator; team members can use it; team admins
                      can manage it.
                      {!isEditing && visibility === "team" && (
                        <span className="block font-medium text-foreground">
                          Choose an owner team before continuing.
                        </span>
                      )}
                    </>
                  }
                  shareHelpText={
                    <>
                      Select which additional teams can access this agent.
                      Members of a shared team can DM it and use it in any Slack
                      channel or Webex space mapped to that team. Team admins
                      can manage shared agents.
                    </>
                  }
                  ownerExtra={
                    !isEditing &&
                    visibility === "team" &&
                    availableTeams.length === 0 ? (
                      <p className="text-xs text-destructive">
                        You must belong to at least one team to create a
                        team-owned agent.
                      </p>
                    ) : null
                  }
                  betweenOwnerAndShare={
                    <div className="space-y-2">
                      <Label>Visibility</Label>
                      {isPlatformDefault && (
                        <p
                          className="text-xs text-amber-600 dark:text-amber-400"
                          data-testid="platform-default-visibility-note"
                        >
                          This agent is the platform default for new chats, so
                          every signed-in user can use it. Change the platform
                          default in Admin → Settings before changing its
                          visibility.
                        </p>
                      )}
                      <div className="grid grid-cols-2 gap-2">
                        {VISIBILITY_OPTIONS.map((opt) => {
                          // When this agent is the platform default, lock the
                          // selector so the admin can't try to demote
                          // `global → team` here — the BFF will reject it.
                          const lockedByPlatformDefault = isPlatformDefault;
                          return (
                            <button
                              key={opt.value}
                              type="button"
                              onClick={() => setVisibility(opt.value)}
                              className={`p-3 rounded-lg border text-left transition-colors ${
                                visibility === opt.value
                                  ? "border-primary bg-primary/5"
                                  : "border-muted hover:border-primary/50"
                              } ${lockedByPlatformDefault ? "opacity-60 cursor-not-allowed" : ""}`}
                              disabled={
                                loading || !!readOnly || lockedByPlatformDefault
                              }
                            >
                              <div className="flex items-center gap-2 mb-1">
                                {opt.icon}
                                <span className="font-medium text-sm">
                                  {opt.label}
                                </span>
                              </div>
                              <div className="text-xs text-muted-foreground">
                                {opt.description}
                              </div>
                            </button>
                          );
                        })}
                      </div>
                      {visibility === "global" && (
                        <div
                          role="note"
                          aria-label="Global visibility summary"
                          className="space-y-1 rounded-lg border bg-muted/30 p-3 text-xs"
                          data-testid="global-visibility-grant-preview"
                        >
                          <div className="font-medium text-foreground">
                            Everyone can use this agent
                            {isPlatformDefault
                              ? " (it is also the platform default)"
                              : ""}
                            .
                          </div>
                          <p className="text-muted-foreground">
                            When you save, every signed-in user will be able to
                            chat with this agent. Owner-team admins still manage
                            it.
                          </p>
                        </div>
                      )}
                    </div>
                  }
                />
              </div>
            )}

            {/* Instructions Step */}
            {activeStep === "instructions" && (
              <div className="space-y-4 pt-2">
                <div className="space-y-2">
                  <div className="flex items-center justify-between relative">
                    <Label htmlFor="systemPrompt">
                      System Prompt <span className="text-destructive">*</span>
                    </Label>
                    <div className="flex items-center gap-2">
                      <div className="relative">
                        <Button
                          type="button"
                          variant="outline"
                          size="sm"
                          className="h-7 text-xs gap-1 px-2 border-primary/30 text-primary hover:bg-primary/10"
                          disabled={!canSuggest || loading || !!readOnly}
                          onClick={() => {
                            setShowSuggestPromptInput((v) => {
                              if (!v) setEnhanceExisting(!!systemPrompt.trim());
                              return !v;
                            });
                            setShowSuggestBasicInput(false);
                          }}
                          title={
                            !name.trim()
                              ? "Enter a name first"
                              : !modelId
                                ? "Select a model first"
                                : "Generate system prompt with AI"
                          }
                        >
                          {generatingField === "system_prompt" ? (
                            <Loader2 className="h-3 w-3 animate-spin" />
                          ) : (
                            <Sparkles className="h-3 w-3" />
                          )}
                          AI Suggest
                        </Button>

                        {/* Inline popover for instructions */}
                        <AnimatePresence>
                          {showSuggestPromptInput && (
                            <motion.div
                              initial={{ opacity: 0, y: -4, scale: 0.95 }}
                              animate={{ opacity: 1, y: 0, scale: 1 }}
                              exit={{ opacity: 0, y: -4, scale: 0.95 }}
                              transition={{ duration: 0.15 }}
                              className="absolute top-full right-0 mt-1 z-50 w-80 p-3 rounded-lg border border-border/50 bg-background shadow-xl"
                            >
                              <label className="text-xs font-medium text-muted-foreground mb-1.5 block">
                                What should the system prompt cover?
                              </label>
                              <Input
                                autoFocus
                                value={suggestPromptInstruction}
                                onChange={(e) =>
                                  setSuggestPromptInstruction(e.target.value)
                                }
                                onKeyDown={(e) => {
                                  if (e.key === "Enter")
                                    handleSuggestSystemPrompt(
                                      suggestPromptInstruction.trim() ||
                                        undefined,
                                    );
                                  if (e.key === "Escape")
                                    setShowSuggestPromptInput(false);
                                }}
                                placeholder="e.g., Focus on step-by-step reasoning..."
                                className="h-8 text-sm mb-2"
                              />
                              {systemPrompt.trim() && (
                                <label className="flex items-center gap-2 mb-2 cursor-pointer">
                                  <input
                                    type="checkbox"
                                    checked={enhanceExisting}
                                    onChange={(e) =>
                                      setEnhanceExisting(e.target.checked)
                                    }
                                    className="rounded border-muted"
                                  />
                                  <span className="text-xs text-muted-foreground">
                                    Enhance existing text
                                  </span>
                                </label>
                              )}
                              <div className="flex items-center gap-1 mb-2">
                                <button
                                  type="button"
                                  className={cn(
                                    "px-2 py-0.5 text-xs rounded-full border transition-colors",
                                    promptStyle === "concise"
                                      ? "border-primary text-primary bg-primary/10"
                                      : "border-border text-muted-foreground hover:border-primary/30",
                                  )}
                                  onClick={() => setPromptStyle("concise")}
                                >
                                  Concise
                                </button>
                                <button
                                  type="button"
                                  className={cn(
                                    "px-2 py-0.5 text-xs rounded-full border transition-colors",
                                    promptStyle === "comprehensive"
                                      ? "border-primary text-primary bg-primary/10"
                                      : "border-border text-muted-foreground hover:border-primary/30",
                                  )}
                                  onClick={() =>
                                    setPromptStyle("comprehensive")
                                  }
                                >
                                  Comprehensive
                                </button>
                              </div>
                              <div className="flex justify-end gap-1.5">
                                <Button
                                  type="button"
                                  variant="ghost"
                                  size="sm"
                                  className="h-7 text-xs"
                                  onClick={() =>
                                    setShowSuggestPromptInput(false)
                                  }
                                >
                                  Cancel
                                </Button>
                                <Button
                                  type="button"
                                  size="sm"
                                  className="h-7 text-xs gap-1 gradient-primary text-white"
                                  onClick={() =>
                                    handleSuggestSystemPrompt(
                                      suggestPromptInstruction.trim() ||
                                        undefined,
                                    )
                                  }
                                >
                                  <Sparkles className="h-3 w-3" />
                                  Generate
                                </Button>
                              </div>
                            </motion.div>
                          )}
                        </AnimatePresence>
                      </div>
                      {/* AI Review button — sibling to AI Suggest. Renders disabled
                      when the target isn't configured; the panel below renders
                      null in the same case so this is the only visible affordance. */}
                      <AiReviewButton review={review} size="sm" />
                    </div>
                  </div>

                  {/* Edit / Preview tabs */}
                  <div className="flex items-center gap-1 border-b border-border/30">
                    <button
                      type="button"
                      onClick={() => setPromptTab("edit")}
                      className={cn(
                        "flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium border-b-2 transition-colors -mb-px",
                        promptTab === "edit"
                          ? "border-primary text-primary"
                          : "border-transparent text-muted-foreground hover:text-foreground",
                      )}
                    >
                      <Pencil className="h-3 w-3" />
                      Edit
                    </button>
                    <button
                      type="button"
                      onClick={() => setPromptTab("preview")}
                      className={cn(
                        "flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium border-b-2 transition-colors -mb-px",
                        promptTab === "preview"
                          ? "border-primary text-primary"
                          : "border-transparent text-muted-foreground hover:text-foreground",
                      )}
                    >
                      <Eye className="h-3 w-3" />
                      Preview
                    </button>
                  </div>

                  {/* Editor column + AI Review panel side-by-side. Panel renders
                    null when the target isn't configured / disabled, so the
                    flex container collapses to just the editor in that case. */}
                  <div className="flex gap-3 min-h-0">
                    <div className="flex-1 min-w-0">
                      {promptTab === "edit" ? (
                        <div
                          className={cn("rounded-lg overflow-hidden border", editorIsLight ? "border-border bg-white" : "border-border/30 bg-[#1e1e2e]")}
                          style={{ height: `${editorHeight}px` }}
                        >
                          <React.Suspense
                            fallback={
                              <div className="flex items-center justify-center h-48 text-zinc-500">
                                <Loader2 className="h-4 w-4 animate-spin mr-2" />
                                <span className="text-sm">
                                  Loading editor...
                                </span>
                              </div>
                            }
                          >
                            <CodeMirrorEditor
                              value={systemPrompt}
                              onChange={(val: string) => setSystemPrompt(val)}
                              extensions={cmExtensions}
                              theme={editorIsLight ? "light" : "dark"}
                              height={`${editorHeight}px`}
                              style={{ fontSize: "15px" }}
                              basicSetup={{
                                lineNumbers: true,
                                foldGutter: true,
                                highlightActiveLine: true,
                                bracketMatching: true,
                                autocompletion: false,
                                indentOnInput: true,
                              }}
                              placeholder="You are a helpful AI assistant that specializes in..."
                              editable={
                                !loading &&
                                !readOnly &&
                                generatingField !== "system_prompt"
                              }
                            />
                          </React.Suspense>
                        </div>
                      ) : (
                        <div
                          className="rounded-lg border p-4 overflow-y-auto prose prose-sm dark:prose-invert max-w-none"
                          style={{ height: `${editorHeight}px` }}
                        >
                          {systemPrompt.trim() ? (
                            <ReactMarkdown
                              remarkPlugins={[remarkGfm]}
                              components={getMarkdownComponents()}
                            >
                              {systemPrompt}
                            </ReactMarkdown>
                          ) : (
                            <p className="text-muted-foreground italic text-sm">
                              Nothing to preview. Switch to Edit to write your
                              system prompt.
                            </p>
                          )}
                        </div>
                      )}

                      {/* Drag handle to resize editor */}
                      <div
                        onMouseDown={handleDragStart}
                        className="flex items-center justify-center h-3 cursor-row-resize group hover:bg-muted/50 rounded-b-lg transition-colors"
                      >
                        <GripHorizontal className="h-3 w-3 text-muted-foreground/40 group-hover:text-muted-foreground" />
                      </div>
                    </div>
                    <AiReviewPanel
                      review={review}
                      style={{ height: `${editorHeight + 12}px` }}
                      onClickAnchor={(anchor) => {
                        // Phase 1: no-op stub. A follow-up will scroll the
                        // CodeMirror view to `anchor.line_start` and flash a
                        // gutter decoration. Logging keeps the wiring observable
                        // during development.
                        if (process.env.NODE_ENV !== "production") {
                          console.debug("[ai-review] anchor click", anchor);
                        }
                      }}
                    />
                  </div>

                  <p className="text-sm text-muted-foreground">
                    Define your agent&apos;s behavior, personality, and
                    capabilities.
                  </p>

                  {blockingMessage && (
                    <div className="rounded-lg bg-destructive/10 border border-destructive/30 p-3">
                      <p className="text-sm text-destructive">
                        {blockingMessage}
                      </p>
                    </div>
                  )}
                </div>
              </div>
            )}

            {/* Tools Step */}
            {activeStep === "tools" && (
              <div className="space-y-6 pt-2">
                {!isDeepAgents && supportsTools && !supportsApprovals && (
                  <p className="text-sm text-muted-foreground">
                    Selected MCP tools run without per-call approval. Your agent and account permissions are checked for each call.
                  </p>
                )}
                {/* Built-in Tools */}
                {supportsBuiltinTools && <BuiltinToolsPicker
                  value={builtinTools}
                  onChange={setBuiltinTools}
                  disabled={loading || !!readOnly}
                />}

                {/* MCP Tools */}
                <div className="space-y-4">
                  <div>
                    <Label>MCP Tool Access</Label>
                    <p className="text-xs text-muted-foreground mb-4">
                      Select which MCP servers and tools this agent can use. If
                      no servers are selected, the agent will have no external
                      tool access.
                    </p>
                  </div>

                  <AllowedToolsPicker
                    value={allowedTools}
                    onChange={setAllowedTools}
                    disabled={loading || !!readOnly}
                  />
                </div>
              </div>
            )}

            {/* Knowledge Step */}
            {activeStep === "knowledge" && (
              <div className="space-y-4 pt-2">
                {!hasRagToolAccess(allowedTools) && (
                  <div className="flex items-center justify-between gap-4 rounded-lg border border-amber-500/30 bg-amber-500/10 px-4 py-3">
                    <p className="text-sm text-amber-800 dark:text-amber-200">
                      To enable access to knowledge bases, enable the Knowledge
                      Base tool calls in the previous step.
                    </p>
                    <Button
                      type="button"
                      variant="outline"
                      size="sm"
                      className="shrink-0"
                      onClick={() => selectStep("tools")}
                    >
                      Go to Tools
                    </Button>
                  </div>
                )}
                <div
                  className={cn(
                    "transition-opacity",
                    !hasRagToolAccess(allowedTools) && "opacity-50",
                  )}
                >
                  <div className="mb-4 flex items-center justify-between gap-4 rounded-lg border p-3">
                    <div>
                      <Label htmlFor="restrict-knowledge">
                        Restrict to collections or datasources
                      </Label>
                      <p className="text-xs text-muted-foreground">
                        {restrictKnowledge
                          ? "This agent's RAG tools are pinned to the picks below. Every query still respects the calling user's own Search access."
                          : "Off: this agent searches everything the calling user can already access, with no additional restriction. Turn this on to pin it to specific collections or datasources instead."}
                      </p>
                    </div>
                    <Switch
                      id="restrict-knowledge"
                      checked={restrictKnowledge}
                      disabled={
                        loading || !!readOnly || !hasRagToolAccess(allowedTools)
                      }
                      onCheckedChange={setRestrictKnowledge}
                    />
                  </div>
                  {restrictKnowledge && (
                    <DatasourcePicker
                      ownerTeamSlug={ownerTeamSlug}
                      value={datasourceIds}
                      onChange={setDatasourceIds}
                      collectionValue={ragCollectionIds}
                      onCollectionChange={setRagCollectionIds}
                      disabled={
                        loading || !!readOnly || !hasRagToolAccess(allowedTools)
                      }
                    />
                  )}
                </div>
              </div>
            )}

            {/* Step: Skills */}
            {activeStep === "skills" && (
              <div className="space-y-4">
                <div>
                  <p className="text-sm text-muted-foreground">
                    Skills provide specialized instructions and workflows that
                    guide your agent&apos;s behavior for specific tasks. The
                    agent reads skill content on demand via progressive
                    disclosure.
                  </p>
                </div>

                <SkillsSelector
                  value={skills}
                  onChange={setSkills}
                  disabled={loading || !!readOnly}
                />
              </div>
            )}

            {/* Advanced Step */}
            {activeStep === "advanced" && (isDeepAgents || hasFeaturePanels ? (
              <AdvancedStep
                descriptor={selectedHarness}
                schemaPanel={selectedHarness && !isDeepAgents ? <HarnessOptionsForm schema={selectedHarness.options_schema} value={harnessOptions} onChange={setHarnessOptions} disabled={loading || !!readOnly} /> : undefined}
                agent={agent}
                subagents={subagents}
                setSubagents={setSubagents}
                allowedRemoteAgents={allowedRemoteAgents}
                setAllowedRemoteAgents={setAllowedRemoteAgents}
                remoteAgentTimeouts={remoteAgentTimeouts}
                setRemoteAgentTimeouts={setRemoteAgentTimeouts}
                interruptOn={interruptOn}
                setInterruptOn={setInterruptOn}
                allowedTools={allowedTools}
                builtinTools={builtinTools}
                setBuiltinTools={(v) => setBuiltinTools(v)}
                features={features}
                setFeatures={setFeatures}
                availableModels={availableModels}
                setMiddlewareError={setMiddlewareError}
                loading={loading || !!readOnly}
                visibility={visibility}
              />
            ) : (
              <div className="space-y-4 pt-2">
                <div>
                  <Label>{selectedHarness?.display_name} options</Label>
                  <p className="text-xs text-muted-foreground">Configure the options available for the selected operator profile.</p>
                </div>
                {selectedHarness && (
                  <HarnessOptionsForm
                    schema={selectedHarness.options_schema}
                    value={harnessOptions}
                    onChange={setHarnessOptions}
                    disabled={loading || !!readOnly}
                  />
                )}
              </div>
            ))}

            {/* Error */}
            {error && (
              <div
                role="alert"
                className="rounded-lg bg-destructive/10 border border-destructive/30 p-3 space-y-2"
              >
                <p className="text-sm text-destructive">{error}</p>
                {transferNeedsServerConfirm && (
                  <Button
                    type="button"
                    size="sm"
                    variant="destructive"
                    disabled={loading}
                    onClick={() => {
                      setTransferConfirmedNotMember(true);
                      void handleSubmit(undefined, {
                        forceConfirmNotMember: true,
                      });
                    }}
                  >
                    Confirm Transfer
                  </Button>
                )}
              </div>
            )}
          </fieldset>

          {/* Step Navigation - Right aligned */}
          <div className="flex items-center justify-end gap-2 pt-4 border-t">
            <Button
              type="button"
              variant="outline"
              onClick={goToPreviousStep}
              disabled={currentStepIndex === 0 || loading}
              size="sm"
            >
              <ChevronLeft className="h-4 w-4 mr-1" />
              Previous
            </Button>
            <Button
              type="button"
              variant="outline"
              onClick={() => void goToNextStep()}
              disabled={currentStepIndex === steps.length - 1 || loading}
              size="sm"
            >
              Next
              <ChevronRight className="h-4 w-4 ml-1" />
            </Button>
          </div>
        </form>
      </CardContent>

      {/* Action Buttons - Outside the card content */}
      <div className="flex items-center gap-2 px-6 py-4 border-t bg-muted/30">
        <div className="text-xs text-muted-foreground mr-auto hidden sm:block">
          {readOnly ? (
            "This agent is config-driven and cannot be modified"
          ) : isDeepAgents ? (
            <>
              {builtinTools?.fetch_url?.enabled ? "1 built-in, " : ""}
              {Object.keys(allowedTools).length} MCP server(s),{" "}
              {subagents.length} subagent(s)
            </>
          ) : (
            <>{selectedHarness?.display_name ?? harnessId} · {selectedHarness?.profiles.find((profile) => profile.id === harnessProfileId)?.display_name || "Select an operator profile"}</>
          )}
        </div>
        <Button
          type="button"
          variant="outline"
          onClick={onCancel}
          disabled={loading}
        >
          {readOnly ? "Close" : "Cancel"}
        </Button>
        {builderSchemaError && <p role="alert" className="text-sm text-destructive">{builderSchemaError}</p>}
        {!readOnly && (
          <Button
            onClick={handleSubmit}
            disabled={loading || !isValid || Boolean(builderSchemaError) || (requiresHarnessConfirmation && !harnessSwitchConfirmed)}
            // Native-tooltip mirror of the inline hint above. Helps users who
            // hover the button looking for an explanation when they miss the
            // inline text (e.g. on narrow screens where the hint wraps).
            title={
              !loading && firstBlocker
                ? `${firstBlocker.label} is required${blockerStepLabel ? ` (on ${blockerStepLabel} step)` : ""}`
                : undefined
            }
          >
            {loading ? (
              <>
                <Loader2 className="h-4 w-4 mr-2 animate-spin" />
                {isEditing ? "Saving..." : "Creating..."}
              </>
            ) : isEditing ? (
              "Save Changes"
            ) : (
              "Create Agent"
            )}
          </Button>
        )}
      </div>

      <ConnectAgentCoreDialog open={connectionDialogOpen} onOpenChange={setConnectionDialogOpen} onConnected={selectConnectedAgentCore} />
      <UnsavedChangesDialog
        open={pendingClose}
        onCancel={handleCancelDiscard}
        onDiscard={handleConfirmDiscard}
        title="Unsaved changes"
        description="You have unsaved changes in the agent editor. They will be lost if you leave now."
      />
    </Card>
  );
}
