import type {
  AgentComposerBindingResponse,
  DeclaredOutputConfig,
  WorkflowAgentComposerResponse,
} from '@dify/contracts/api/console/apps/types.gen'
import type { AgentRosterNodeData } from '../../block-selector/types'
import type { NodePanelProps } from '../../types'
import type { AgentV2NodeType } from './types'
import type { AgentOutputTypeOptionValue } from '@/app/components/base/prompt-editor/plugins/agent-output-block/utils'
import { useMutation } from '@tanstack/react-query'
import { produce } from 'immer'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { createPortal } from 'react-dom'
import { useTranslation } from 'react-i18next'
import {
  createAgentOutputConfig,
  extractAgentOutputNames,
  replaceAgentOutputName,
} from '@/app/components/base/prompt-editor/plugins/agent-output-block/utils'
import { useHooksStore } from '@/app/components/workflow/hooks-store'
import { useStore } from '@/app/components/workflow/store'
import { consoleQuery } from '@/service/client'
import { FlowType } from '@/types/common'
import { useNodeDataUpdate } from '../../hooks/use-node-data-update'
import useNodeCrud from '../_base/hooks/use-node-crud'
import {
  WorkflowInlineAgentConfigureWorkspace,
  WorkflowRosterAgentOrchestratePanelContent,
} from './components/agent-orchestrate-panel-content'
import { AgentOutputVariables } from './components/agent-output-variables'
import { OutputEditCard } from './components/agent-output-variables/edit-card'
import { createDraft, isDefaultOutput } from './components/agent-output-variables/utils'
import { AgentRosterField } from './components/agent-roster-field'
import { AgentTaskField } from './components/agent-task-field'
import { SaveInlineAgentToRosterDialog } from './components/save-inline-agent-to-roster-dialog'
import {
  useAgentRosterDetail,
  useCreateInlineAgentBinding,
  useWorkflowInlineAgentDetail,
} from './hooks'
import {
  AGENT_V2_RESERVED_OUTPUT_NAMES,
  getAgentV2CustomDeclaredOutputs,
  getAgentV2DeclaredOutputs,
  normalizeAgentV2DeclaredOutputs,
} from './output-variables'
import { hasValidInlineAgentBinding } from './types'

function FloatingOutputEditor({
  agentTask,
  editOutputName,
  editOutputRequestKey,
  editOutputType,
  outputs,
  position,
  onCancel,
  onChange,
}: {
  agentTask?: string
  editOutputName?: string
  editOutputRequestKey?: number
  editOutputType?: AgentOutputTypeOptionValue
  outputs: DeclaredOutputConfig[]
  position?: { left: number; top: number }
  onCancel: () => void
  onChange: (outputs: DeclaredOutputConfig[], agentTask?: string) => void
}) {
  if (!editOutputName || !position || typeof document === 'undefined') return null

  const outputIndex = outputs.findIndex((output) => output.name === editOutputName)
  const existingOutput = outputs[outputIndex]
  if (existingOutput && isDefaultOutput(existingOutput)) return null

  const output =
    existingOutput ?? createAgentOutputConfig(editOutputName, editOutputType ?? 'string')
  const isExistingOutput = !!existingOutput

  return createPortal(
    <div
      className="fixed z-50 w-100"
      style={{
        left: position.left,
        top: position.top,
      }}
    >
      <OutputEditCard
        key={`${editOutputRequestKey ?? 0}-${output.name}`}
        editingIndex={isExistingOutput ? outputIndex : undefined}
        existingOutputs={outputs}
        reservedNames={AGENT_V2_RESERVED_OUTPUT_NAMES}
        state={{
          ...(isExistingOutput ? { outputIndex } : {}),
          draft: createDraft(output),
        }}
        onCancel={onCancel}
        onConfirm={(nextOutput) => {
          const currentAgentTask = agentTask || ''
          const nextAgentTask =
            nextOutput.name !== editOutputName &&
            extractAgentOutputNames(currentAgentTask).has(editOutputName)
              ? replaceAgentOutputName(currentAgentTask, editOutputName, nextOutput.name)
              : undefined

          onChange(
            isExistingOutput
              ? outputs.map((item, index) => (index === outputIndex ? nextOutput : item))
              : [...outputs.filter((output) => output.name !== editOutputName), nextOutput],
            nextAgentTask,
          )
          onCancel()
        }}
      />
    </div>,
    document.body,
  )
}

export function AgentV2Panel({ id, data }: NodePanelProps<AgentV2NodeType>) {
  const { t } = useTranslation()
  const { inputs, setInputs } = useNodeCrud<AgentV2NodeType>(id, data)
  const inputsRef = useRef(inputs)
  const promptOutputNamesRef = useRef(extractAgentOutputNames(inputs.agent_task || ''))
  const [isRosterAgentPanelOpen, setIsRosterAgentPanelOpen] = useState(false)
  const [isInlineAgentPanelOpenedFromTrigger, setIsInlineAgentPanelOpenedFromTrigger] =
    useState(false)
  const [isSaveToRosterDialogOpen, setIsSaveToRosterDialogOpen] = useState(false)
  const [editingOutputFromTask, setEditingOutputFromTask] = useState<{
    name: string
    outputType: AgentOutputTypeOptionValue
    position: { left: number; top: number }
    requestKey: number
  } | null>(null)
  const [isOutputVariablesCollapsed, setIsOutputVariablesCollapsed] = useState(true)
  const { handleNodeDataUpdate, handleNodeDataUpdateWithSyncDraft } = useNodeDataUpdate()
  const openInlineAgentPanelNodeId = useStore((state) => state.openInlineAgentPanelNodeId)
  const setOpenInlineAgentPanelNodeId = useStore((state) => state.setOpenInlineAgentPanelNodeId)
  const configsMap = useHooksStore((state) => state.configsMap)
  const drawerPortalContainerRef = useRef<HTMLDivElement>(null)
  const [localDeclaredOutputs, setLocalDeclaredOutputs] = useState<DeclaredOutputConfig[] | null>(
    null,
  )
  const normalizedDeclaredOutputs = useMemo(
    () => normalizeAgentV2DeclaredOutputs(inputs.agent_declared_outputs ?? []),
    [inputs.agent_declared_outputs],
  )
  const declaredOutputs = localDeclaredOutputs ?? normalizedDeclaredOutputs
  const rosterAgentId =
    inputs.agent_binding?.binding_type === 'roster_agent'
      ? inputs.agent_binding.agent_id
      : undefined
  const sourceInlineAgentId =
    inputs.agent_binding?.binding_type === 'inline_agent'
      ? inputs.agent_binding.agent_id
      : undefined
  const isInlineAgentReady = hasValidInlineAgentBinding(inputs)
  const isInlineAgentPending =
    inputs.agent_binding?.binding_type === 'inline_agent' && !isInlineAgentReady
  const rosterAgentQuery = useAgentRosterDetail(rosterAgentId)
  const inlineAgentQuery = useWorkflowInlineAgentDetail(id, sourceInlineAgentId, {
    pollUntilReady: isInlineAgentReady,
  })
  const { createInlineAgentBinding, isCreatingInlineAgent } = useCreateInlineAgentBinding()
  const inlineAgent = inlineAgentQuery.data?.agent
  const inlineAgentBinding = inlineAgentQuery.data?.binding
  const inlineAgentId =
    inlineAgentBinding?.binding_type === 'inline_agent' && inlineAgentBinding.agent_id
      ? inlineAgentBinding.agent_id
      : sourceInlineAgentId
  const isInlineAgentCreated = isInlineAgentReady && !!inlineAgent
  const isInlineAgentLoadError = isInlineAgentReady && inlineAgentQuery.isError
  const isInlineAgentWaitingForCreation =
    isInlineAgentReady && !isInlineAgentCreated && !isInlineAgentLoadError
  const isInlineAgentPanelOpen =
    (isInlineAgentCreated || isInlineAgentPending) && openInlineAgentPanelNodeId === id
  const { isPending: isAppCopyingFromRoster, mutate: copyFromRosterApp } = useMutation(
    consoleQuery.apps.byAppId.workflows.draft.nodes.byNodeId.agentComposer.copyFromRoster.post.mutationOptions(),
  )
  const { isPending: isSnippetCopyingFromRoster, mutate: copyFromRosterSnippet } = useMutation(
    consoleQuery.snippets.bySnippetId.workflows.draft.nodes.byNodeId.agentComposer.copyFromRoster.post.mutationOptions(),
  )
  const isCopyingFromRoster = isAppCopyingFromRoster || isSnippetCopyingFromRoster
  const isAgentPanelOpen =
    isInlineAgentCreated || isInlineAgentPending ? isInlineAgentPanelOpen : isRosterAgentPanelOpen
  const isInlineAgentLoading = isInlineAgentPending || isInlineAgentWaitingForCreation
  const isAgentBindingPending =
    isInlineAgentPending || isInlineAgentWaitingForCreation || isCreatingInlineAgent
  const canStartFromScratch = inputs.agent_binding?.binding_type !== 'inline_agent'
  const saveToRosterTarget =
    configsMap?.flowId &&
    (configsMap.flowType === FlowType.appFlow || configsMap.flowType === FlowType.snippet)
      ? { flowId: configsMap.flowId, flowType: configsMap.flowType }
      : null
  const canSaveInlineToRoster = isInlineAgentReady && !!inlineAgent && !!saveToRosterTarget
  const inlineComposerStateForPanel = inlineAgentQuery.data
  const displayedAgent =
    rosterAgentQuery.data ??
    (isInlineAgentPending || isInlineAgentReady
      ? {
          id: inlineAgentId ?? id,
          name:
            inlineAgent?.name ||
            t(($) => $['nodes.agent.roster.inlineSetup.name'], { ns: 'workflow' }),
          description: inlineAgent?.description,
          role: t(($) => $['nodes.agent.roster.inlineSetup.type'], { ns: 'workflow' }),
        }
      : undefined)

  useEffect(() => {
    inputsRef.current = inputs
    promptOutputNamesRef.current = extractAgentOutputNames(inputs.agent_task || '')
  }, [inputs])

  useEffect(() => {
    if (!inputs._openInlineAgentPanel || !isInlineAgentReady) return

    setOpenInlineAgentPanelNodeId(id)
    handleNodeDataUpdate({
      id,
      data: {
        _openInlineAgentPanel: false,
      },
    })
  }, [
    handleNodeDataUpdate,
    id,
    inputs._openInlineAgentPanel,
    isInlineAgentReady,
    setOpenInlineAgentPanelNodeId,
  ])

  const handleTaskChange = useCallback(
    (value: string) => {
      const currentPromptOutputNames = extractAgentOutputNames(value)
      const removedPromptOutputNames = [...promptOutputNamesRef.current].filter(
        (name) => !currentPromptOutputNames.has(name),
      )
      const addedPromptOutputNames = [...currentPromptOutputNames].filter(
        (name) => !promptOutputNamesRef.current.has(name),
      )
      const newInputs = produce(inputsRef.current, (draft) => {
        draft.agent_task = value
        if (removedPromptOutputNames.length) {
          const currentDeclaredOutputs = getAgentV2CustomDeclaredOutputs(
            draft.agent_declared_outputs ?? [],
          )
          if (
            removedPromptOutputNames.length === 1 &&
            addedPromptOutputNames.length === 1 &&
            !AGENT_V2_RESERVED_OUTPUT_NAMES.has(removedPromptOutputNames[0]!) &&
            !AGENT_V2_RESERVED_OUTPUT_NAMES.has(addedPromptOutputNames[0]!)
          ) {
            const oldName = removedPromptOutputNames[0]!
            const nextName = addedPromptOutputNames[0]!
            draft.agent_declared_outputs = currentDeclaredOutputs.map((output) =>
              output.name === oldName ? { ...output, name: nextName } : output,
            )
          } else {
            const removedNameSet = new Set(removedPromptOutputNames)
            draft.agent_declared_outputs = currentDeclaredOutputs.filter(
              (output) => !removedNameSet.has(output.name),
            )
          }
        }
      })
      inputsRef.current = newInputs
      promptOutputNamesRef.current = currentPromptOutputNames
      if (removedPromptOutputNames.length)
        setLocalDeclaredOutputs(getAgentV2DeclaredOutputs(newInputs))
      setInputs(newInputs)
    },
    [setInputs],
  )

  const handleRosterChange = useCallback(
    (agent: AgentRosterNodeData) => {
      setOpenInlineAgentPanelNodeId(undefined)
      const newInputs = produce(inputs, (draft) => {
        delete (draft as AgentV2NodeType & { agent_roster?: unknown }).agent_roster
        draft.agent_binding = {
          binding_type: 'roster_agent',
          agent_id: agent.id,
        }
      })
      handleNodeDataUpdateWithSyncDraft(
        {
          id,
          data: newInputs,
        },
        {
          sync: true,
          notRefreshWhenSyncError: true,
        },
      )
    },
    [handleNodeDataUpdateWithSyncDraft, id, inputs, setOpenInlineAgentPanelNodeId],
  )

  const handleRosterCopySuccess = useCallback(
    (composerState: WorkflowAgentComposerResponse) => {
      const binding = composerState.binding
      if (
        binding?.binding_type !== 'inline_agent' ||
        !binding.agent_id ||
        !binding.current_snapshot_id
      ) {
        return
      }

      setIsRosterAgentPanelOpen(false)
      setIsInlineAgentPanelOpenedFromTrigger(true)
      setOpenInlineAgentPanelNodeId(id)

      const newInputs = produce(inputsRef.current, (draft) => {
        delete (draft as AgentV2NodeType & { agent_roster?: unknown }).agent_roster
        draft.agent_binding = {
          binding_type: 'inline_agent',
          agent_id: binding.agent_id,
          current_snapshot_id: binding.current_snapshot_id,
        }
        draft._openInlineAgentPanel = true
      })
      inputsRef.current = newInputs
      handleNodeDataUpdateWithSyncDraft(
        {
          id,
          data: newInputs,
        },
        {
          sync: true,
          notRefreshWhenSyncError: true,
        },
      )
    },
    [handleNodeDataUpdateWithSyncDraft, id, setOpenInlineAgentPanelNodeId],
  )

  const handleMakeRosterCopy = useCallback(() => {
    if (!configsMap?.flowId || !rosterAgentId || isCopyingFromRoster) return

    const body = {
      source_agent_id: rosterAgentId,
    }
    const options = {
      onSuccess: handleRosterCopySuccess,
    }

    if (configsMap.flowType === FlowType.snippet) {
      copyFromRosterSnippet(
        {
          params: {
            snippet_id: configsMap.flowId,
            node_id: id,
          },
          body,
        },
        options,
      )
      return
    }

    if (configsMap.flowType === FlowType.appFlow) {
      copyFromRosterApp(
        {
          params: {
            app_id: configsMap.flowId,
            node_id: id,
          },
          body,
        },
        options,
      )
    }
  }, [
    configsMap?.flowId,
    configsMap?.flowType,
    copyFromRosterApp,
    copyFromRosterSnippet,
    handleRosterCopySuccess,
    id,
    isCopyingFromRoster,
    rosterAgentId,
  ])

  const handleSaveInlineToRosterOpen = useCallback(() => {
    setIsSaveToRosterDialogOpen(true)
  }, [])

  const handleInlineSavedToRoster = useCallback(
    (agentId: string) => {
      setOpenInlineAgentPanelNodeId(undefined)
      setIsInlineAgentPanelOpenedFromTrigger(false)
      setIsRosterAgentPanelOpen(true)

      const newInputs = produce(inputsRef.current, (draft) => {
        delete (draft as AgentV2NodeType & { agent_roster?: unknown }).agent_roster
        delete draft._openInlineAgentPanel
        draft.agent_binding = {
          binding_type: 'roster_agent',
          agent_id: agentId,
        }
      })
      inputsRef.current = newInputs
      handleNodeDataUpdateWithSyncDraft(
        {
          id,
          data: newInputs,
        },
        {
          sync: true,
          notRefreshWhenSyncError: true,
        },
      )
    },
    [handleNodeDataUpdateWithSyncDraft, id, setOpenInlineAgentPanelNodeId],
  )

  const handleInlineAgentBindingCreated = useCallback(
    (binding: { agent_id: string; binding_type: 'inline_agent'; current_snapshot_id: string }) => {
      const newInputs = produce(inputsRef.current, (draft) => {
        delete (draft as AgentV2NodeType & { agent_roster?: unknown }).agent_roster
        draft.agent_binding = binding
        draft._openInlineAgentPanel = true
      })
      inputsRef.current = newInputs
      handleNodeDataUpdateWithSyncDraft(
        {
          id,
          data: newInputs,
        },
        {
          sync: true,
          notRefreshWhenSyncError: true,
        },
      )
    },
    [handleNodeDataUpdateWithSyncDraft, id],
  )

  const handleInlineAgentSaved = useCallback(
    (binding: AgentComposerBindingResponse) => {
      if (
        binding.binding_type !== 'inline_agent' ||
        !binding.agent_id ||
        !binding.current_snapshot_id
      ) {
        return
      }

      const newInputs = produce(inputsRef.current, (draft) => {
        delete (draft as AgentV2NodeType & { agent_roster?: unknown }).agent_roster
        delete draft._openInlineAgentPanel
        draft.agent_binding = {
          binding_type: 'inline_agent',
          agent_id: binding.agent_id,
          current_snapshot_id: binding.current_snapshot_id,
        }
      })
      inputsRef.current = newInputs
      handleNodeDataUpdateWithSyncDraft(
        {
          id,
          data: newInputs,
        },
        {
          sync: true,
          notRefreshWhenSyncError: true,
        },
      )
    },
    [handleNodeDataUpdateWithSyncDraft, id],
  )

  const handleStartFromScratch = useCallback(() => {
    setIsRosterAgentPanelOpen(false)
    setIsInlineAgentPanelOpenedFromTrigger(false)
    setOpenInlineAgentPanelNodeId(id)

    const pendingInputs = produce(inputsRef.current, (draft) => {
      delete (draft as AgentV2NodeType & { agent_roster?: unknown }).agent_roster
      draft.agent_binding = {
        binding_type: 'inline_agent',
      }
      draft._openInlineAgentPanel = true
    })
    inputsRef.current = pendingInputs
    handleNodeDataUpdateWithSyncDraft(
      {
        id,
        data: pendingInputs,
      },
      {
        sync: true,
        notRefreshWhenSyncError: true,
      },
    )

    createInlineAgentBinding(id, {
      onSuccess: handleInlineAgentBindingCreated,
    })
  }, [
    createInlineAgentBinding,
    handleInlineAgentBindingCreated,
    handleNodeDataUpdateWithSyncDraft,
    id,
    setOpenInlineAgentPanelNodeId,
  ])

  const handleAgentPanelOpenChange = useCallback(
    (open: boolean) => {
      if (open && isInlineAgentWaitingForCreation) return

      if (isInlineAgentReady || isInlineAgentPending) {
        if (open) setIsInlineAgentPanelOpenedFromTrigger(true)

        setOpenInlineAgentPanelNodeId(open ? id : undefined)
        if (open && isInlineAgentReady) void inlineAgentQuery.refetch()

        if (open && isInlineAgentPending && !isCreatingInlineAgent) {
          createInlineAgentBinding(id, {
            onSuccess: handleInlineAgentBindingCreated,
          })
        }
        return
      }

      setIsRosterAgentPanelOpen(open)
    },
    [
      createInlineAgentBinding,
      handleInlineAgentBindingCreated,
      id,
      inlineAgentQuery,
      isCreatingInlineAgent,
      isInlineAgentPending,
      isInlineAgentReady,
      isInlineAgentWaitingForCreation,
      setOpenInlineAgentPanelNodeId,
    ],
  )

  const handleDeclaredOutputsChange = useCallback(
    (outputs: ReturnType<typeof getAgentV2DeclaredOutputs>, agentTask?: string) => {
      setIsOutputVariablesCollapsed(false)
      const previousOutputs = getAgentV2DeclaredOutputs(inputsRef.current)
      let nextAgentTask = agentTask
      let nextOutputs = normalizeAgentV2DeclaredOutputs(outputs)
      if (agentTask !== undefined) {
        const nextPromptOutputNames = extractAgentOutputNames(agentTask)
        const removedPromptOutputNames = [...promptOutputNamesRef.current].filter(
          (name) => !nextPromptOutputNames.has(name),
        )
        const addedPromptOutputNames = [...nextPromptOutputNames].filter(
          (name) => !promptOutputNamesRef.current.has(name),
        )

        if (
          removedPromptOutputNames.length === 1 &&
          addedPromptOutputNames.length === 1 &&
          !AGENT_V2_RESERVED_OUTPUT_NAMES.has(removedPromptOutputNames[0]!) &&
          !AGENT_V2_RESERVED_OUTPUT_NAMES.has(addedPromptOutputNames[0]!)
        ) {
          const oldName = removedPromptOutputNames[0]!
          const nextName = addedPromptOutputNames[0]!
          const oldOutputIndex = previousOutputs.findIndex((output) => output.name === oldName)
          const nextOutput = nextOutputs.find((output) => output.name === nextName)
          if (oldOutputIndex >= 0 && nextOutput) {
            nextOutputs = previousOutputs.map((output, index) =>
              index === oldOutputIndex ? nextOutput : output,
            )
          }
        }
      }
      if (nextAgentTask === undefined && previousOutputs.length === nextOutputs.length) {
        const renamedOutputs = previousOutputs
          .map((previousOutput, index) => ({
            oldName: previousOutput.name,
            nextName: nextOutputs[index]?.name,
          }))
          .filter(({ oldName, nextName }) => nextName && oldName !== nextName)

        if (renamedOutputs.length === 1) {
          const { oldName, nextName } = renamedOutputs[0]!
          const currentAgentTask = inputsRef.current.agent_task || ''
          if (extractAgentOutputNames(currentAgentTask).has(oldName))
            nextAgentTask = replaceAgentOutputName(currentAgentTask, oldName, nextName!)
        }
      }

      const customOutputs = getAgentV2CustomDeclaredOutputs(nextOutputs)
      const newInputs = produce(inputsRef.current, (draft) => {
        draft.agent_declared_outputs = customOutputs
        if (nextAgentTask !== undefined) draft.agent_task = nextAgentTask
      })
      inputsRef.current = newInputs
      setLocalDeclaredOutputs(nextOutputs)
      if (nextAgentTask !== undefined)
        promptOutputNamesRef.current = extractAgentOutputNames(nextAgentTask)
      handleNodeDataUpdateWithSyncDraft(
        {
          id,
          data: newInputs,
        },
        {
          sync: true,
          notRefreshWhenSyncError: true,
        },
      )
    },
    [handleNodeDataUpdateWithSyncDraft, id],
  )

  const handleEditTaskOutput = useCallback(
    (name: string, outputType: AgentOutputTypeOptionValue) => {
      setIsOutputVariablesCollapsed(false)
      const panelRect = drawerPortalContainerRef.current?.getBoundingClientRect()
      const editorWidth = 400
      const gap = 24
      const position = {
        left: Math.max(16, (panelRect?.left ?? 0) - editorWidth - gap),
        top: Math.max(16, (panelRect?.top ?? 0) + 144),
      }

      setEditingOutputFromTask((current) => ({
        name,
        outputType,
        position,
        requestKey: (current?.requestKey ?? 0) + 1,
      }))
    },
    [],
  )

  return (
    <div ref={drawerPortalContainerRef} className="relative pt-2">
      <FloatingOutputEditor
        agentTask={inputs.agent_task}
        editOutputName={editingOutputFromTask?.name}
        editOutputRequestKey={editingOutputFromTask?.requestKey}
        editOutputType={editingOutputFromTask?.outputType}
        outputs={declaredOutputs}
        position={editingOutputFromTask?.position}
        onCancel={() => setEditingOutputFromTask(null)}
        onChange={handleDeclaredOutputsChange}
      />
      <div className="border-b border-divider-subtle">
        <AgentRosterField
          agent={
            isInlineAgentWaitingForCreation || isInlineAgentLoadError ? undefined : displayedAgent
          }
          agentId={rosterAgentId ?? inlineAgentId ?? (isInlineAgentPending ? id : undefined)}
          canOpenPanel={!isInlineAgentWaitingForCreation}
          errorMessage={
            isInlineAgentLoadError
              ? t(($) => $['roster.nodeSelector.createInlineFailed'], { ns: 'agentV2' })
              : undefined
          }
          isInlineSetup={isInlineAgentReady || isInlineAgentPending}
          isLoading={isInlineAgentLoading}
          isPanelCopyPending={isCopyingFromRoster}
          isPanelOpen={isAgentPanelOpen}
          isPending={isAgentBindingPending}
          isRetrying={isInlineAgentLoadError && inlineAgentQuery.isFetching}
          panelBody={
            isAgentPanelOpen && displayedAgent ? (
              isInlineAgentReady || isInlineAgentPending ? (
                <WorkflowInlineAgentConfigureWorkspace
                  agentId={inlineAgentId ?? undefined}
                  flowId={configsMap?.flowId}
                  flowType={configsMap?.flowType}
                  inlineComposerState={inlineComposerStateForPanel}
                  nodeId={id}
                  onClose={() => handleAgentPanelOpenChange(false)}
                  onSaved={handleInlineAgentSaved}
                  onSaveInlineToRoster={
                    canSaveInlineToRoster ? handleSaveInlineToRosterOpen : undefined
                  }
                  open={isAgentPanelOpen}
                />
              ) : (
                <WorkflowRosterAgentOrchestratePanelContent
                  agentId={rosterAgentId}
                  nodeId={id}
                  open={isAgentPanelOpen}
                />
              )
            ) : undefined
          }
          panelMode={
            isInlineAgentPending || (isInlineAgentReady && !isInlineAgentPanelOpenedFromTrigger)
              ? 'setup'
              : 'detail'
          }
          portalContainerRef={drawerPortalContainerRef}
          showPanelDetailActions={!isInlineAgentReady && !isInlineAgentPending}
          onChange={handleRosterChange}
          onMakeCopy={rosterAgentId ? handleMakeRosterCopy : undefined}
          onPanelOpenChange={handleAgentPanelOpenChange}
          onRetry={isInlineAgentLoadError ? () => void inlineAgentQuery.refetch() : undefined}
          onSaveInlineToRoster={canSaveInlineToRoster ? handleSaveInlineToRosterOpen : undefined}
          onStartFromScratch={canStartFromScratch ? handleStartFromScratch : undefined}
        />
        {saveToRosterTarget && inlineAgent && (
          <SaveInlineAgentToRosterDialog
            flowId={saveToRosterTarget.flowId}
            flowType={saveToRosterTarget.flowType}
            initialAgent={inlineAgent}
            nodeId={id}
            open={isSaveToRosterDialogOpen}
            onOpenChange={setIsSaveToRosterDialogOpen}
            onSaved={handleInlineSavedToRoster}
          />
        )}
      </div>
      <div
        aria-disabled={isInlineAgentPending}
        inert={isInlineAgentPending ? true : undefined}
        className={isInlineAgentPending ? 'pointer-events-none' : undefined}
      >
        <div className="border-b border-divider-subtle">
          <AgentTaskField
            id={id}
            data={inputs}
            outputs={declaredOutputs}
            onChange={handleTaskChange}
            onEditOutput={handleEditTaskOutput}
            onOutputsChange={handleDeclaredOutputsChange}
          />
        </div>
        <div>
          <AgentOutputVariables
            collapsed={isOutputVariablesCollapsed}
            outputs={declaredOutputs}
            onCollapse={setIsOutputVariablesCollapsed}
            onChange={handleDeclaredOutputsChange}
          />
        </div>
      </div>
    </div>
  )
}
