'use client'

import { useEffect, useRef, useState } from 'react'
import { createLogger } from '@sim/logger'
import { getErrorMessage } from '@sim/utils/errors'
import { ChevronDown, ChevronRight } from 'lucide-react'
import {
  Button,
  ChipInput,
  ChipModal,
  ChipModalBody,
  ChipModalError,
  ChipModalField,
  ChipModalFooter,
  type ChipModalFooterAction,
  ChipModalHeader,
  SecretInput,
} from '@/components/emcn'
import { cn } from '@/lib/core/utils/cn'
import type { McpAuthType, McpTransport } from '@/lib/mcp/types'
import {
  checkEnvVarTrigger,
  EnvVarDropdown,
} from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/env-var-dropdown'
import { formatDisplayText } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/formatted-text'
import { useMcpServerTest } from '@/hooks/queries/mcp'

const logger = createLogger('McpServerFormModal')

interface HeaderEntry {
  key: string
  value: string
}

interface McpServerFormData {
  name: string
  transport: McpTransport
  url?: string
  timeout?: number
  headers?: HeaderEntry[]
  oauthClientId?: string
  oauthClientSecret?: string
  hasOauthClientSecret?: boolean
}

export interface McpServerFormConfig {
  name: string
  transport: McpTransport
  url: string
  headers: Record<string, string>
  timeout: number
  oauthClientId?: string
  oauthClientSecret?: string
  authType?: McpAuthType
}

export interface McpServerFormModalProps {
  open: boolean
  onOpenChange: (open: boolean) => void
  mode: 'add' | 'edit'
  initialData?: McpServerFormData
  onSubmit: (config: McpServerFormConfig) => Promise<void>
  workspaceId: string
  availableEnvVars?: Set<string>
  allowedMcpDomains: string[] | null
}

const ENV_VAR_PATTERN = /\{\{[^}]+\}\}/

function hasEnvVarInHostname(url: string): boolean {
  const globalPattern = new RegExp(ENV_VAR_PATTERN.source, 'g')
  if (url.trim().replace(globalPattern, '').trim() === '') return true
  const protocolEnd = url.indexOf('://')
  if (protocolEnd === -1) return ENV_VAR_PATTERN.test(url)
  const afterProtocol = url.substring(protocolEnd + 3)
  const authorityEnd = afterProtocol.search(/[/?#]/)
  const authority = authorityEnd === -1 ? afterProtocol : afterProtocol.substring(0, authorityEnd)
  return ENV_VAR_PATTERN.test(authority)
}

function isDomainAllowed(url: string | undefined, allowedDomains: string[] | null): boolean {
  if (allowedDomains === null) return true
  if (!url) return false
  if (hasEnvVarInHostname(url)) return true
  try {
    const hostname = new URL(url).hostname.toLowerCase()
    return allowedDomains.includes(hostname)
  } catch {
    return false
  }
}

const DEFAULT_FORM_DATA: McpServerFormData = {
  name: '',
  transport: 'streamable-http',
  url: '',
  timeout: 30000,
  headers: [{ key: '', value: '' }],
}

type InputFieldType = 'url' | 'header-key' | 'header-value'

interface EnvVarDropdownConfig {
  searchTerm: string
  cursorPosition: number
  workspaceId: string
  onSelect: (value: string) => void
  onClose: () => void
}

function getTestButtonLabel(
  testResult: { success: boolean; error?: string; authRequired?: boolean } | null,
  isTestingConnection: boolean
): string {
  if (isTestingConnection) return 'Testing...'
  if (testResult?.success) return 'Connection success'
  if (testResult?.authRequired) return 'Requires OAuth'
  if (testResult && !testResult.success) return 'No connection: retry'
  return 'Test Connection'
}

interface FormattedInputProps {
  ref?: React.RefObject<HTMLInputElement | null>
  placeholder: string
  value: string
  scrollLeft: number
  showEnvVars: boolean
  envVarProps: EnvVarDropdownConfig
  availableEnvVars?: Set<string>
  className?: string
  onChange: (e: React.ChangeEvent<HTMLInputElement>) => void
  onScroll: (scrollLeft: number) => void
}

function FormattedInput({
  ref,
  placeholder,
  value,
  scrollLeft,
  showEnvVars,
  envVarProps,
  availableEnvVars,
  className,
  onChange,
  onScroll,
}: FormattedInputProps) {
  const handleScroll = (e: { currentTarget: HTMLInputElement }) => {
    onScroll(e.currentTarget.scrollLeft)
  }

  return (
    <div className={cn('relative', className)}>
      <ChipInput
        ref={ref}
        placeholder={placeholder}
        value={value}
        onChange={onChange}
        onScroll={handleScroll}
        onInput={handleScroll}
        inputClassName='text-transparent caret-[var(--text-primary)]'
      />
      <div className='pointer-events-none absolute inset-0 flex items-center overflow-hidden px-2 py-1.5 font-medium font-sans text-sm'>
        <div className='whitespace-nowrap' style={{ transform: `translateX(-${scrollLeft}px)` }}>
          {formatDisplayText(value, { availableEnvVars })}
        </div>
      </div>
      {showEnvVars && (
        <EnvVarDropdown
          visible={showEnvVars}
          onSelect={envVarProps.onSelect}
          searchTerm={envVarProps.searchTerm}
          inputValue={value}
          cursorPosition={envVarProps.cursorPosition}
          workspaceId={envVarProps.workspaceId}
          onClose={envVarProps.onClose}
          className='w-full'
          maxHeight='200px'
          style={{ position: 'absolute', top: '100%', left: 0, zIndex: 99999 }}
        />
      )}
    </div>
  )
}

interface HeaderRowProps {
  header: HeaderEntry
  index: number
  headerScrollLeft: Record<string, number>
  showEnvVars: boolean
  activeInputField: InputFieldType | null
  activeHeaderIndex: number | null
  envSearchTerm: string
  cursorPosition: number
  workspaceId: string
  availableEnvVars?: Set<string>
  onInputChange: (field: InputFieldType, value: string, index?: number) => void
  onHeaderScroll: (key: string, scrollLeft: number) => void
  onEnvVarSelect: (value: string) => void
  onEnvVarClose: () => void
}

function HeaderRow({
  header,
  index,
  headerScrollLeft,
  showEnvVars,
  activeInputField,
  activeHeaderIndex,
  envSearchTerm,
  cursorPosition,
  workspaceId,
  availableEnvVars,
  onInputChange,
  onHeaderScroll,
  onEnvVarSelect,
  onEnvVarClose,
}: HeaderRowProps) {
  const isKeyActive =
    showEnvVars && activeInputField === 'header-key' && activeHeaderIndex === index
  const isValueActive =
    showEnvVars && activeInputField === 'header-value' && activeHeaderIndex === index

  const envVarProps: EnvVarDropdownConfig = {
    searchTerm: envSearchTerm,
    cursorPosition,
    workspaceId,
    onSelect: onEnvVarSelect,
    onClose: onEnvVarClose,
  }

  return (
    <div className='relative flex items-center gap-2'>
      <FormattedInput
        placeholder='Name'
        value={header.key || ''}
        scrollLeft={headerScrollLeft[`key-${index}`] || 0}
        showEnvVars={isKeyActive}
        envVarProps={envVarProps}
        availableEnvVars={availableEnvVars}
        className='flex-1'
        onChange={(e) => onInputChange('header-key', e.target.value, index)}
        onScroll={(sl) => onHeaderScroll(`key-${index}`, sl)}
      />

      <FormattedInput
        placeholder='Value'
        value={header.value || ''}
        scrollLeft={headerScrollLeft[`value-${index}`] || 0}
        showEnvVars={isValueActive}
        envVarProps={envVarProps}
        availableEnvVars={availableEnvVars}
        className='flex-1'
        onChange={(e) => onInputChange('header-value', e.target.value, index)}
        onScroll={(sl) => onHeaderScroll(`value-${index}`, sl)}
      />
    </div>
  )
}

function headersToRecord(headers: HeaderEntry[] | undefined): Record<string, string> {
  const record: Record<string, string> = {}
  for (const header of headers || []) {
    if (header.key.trim()) {
      record[header.key] = header.value
    }
  }
  return record
}

function extractStringHeaders(headers: unknown): Record<string, string> {
  if (typeof headers !== 'object' || headers === null) return {}
  return Object.fromEntries(
    Object.entries(headers).filter(
      (entry): entry is [string, string] => typeof entry[1] === 'string'
    )
  )
}

/**
 * Updates a headers array with auto-add (new empty row when typing in last)
 * and auto-remove (drop non-last empty rows).
 */
function updateHeadersArray(
  headers: HeaderEntry[],
  index: number,
  field: 'key' | 'value',
  value: string
): HeaderEntry[] {
  const updated = [...headers]
  if (updated[index]) {
    updated[index] = { ...updated[index], [field]: value }
  }

  const lastIdx = updated.length - 1
  if (index === lastIdx && updated[lastIdx] && (updated[lastIdx].key || updated[lastIdx].value)) {
    updated.push({ key: '', value: '' })
  }

  const lastIndex = updated.length - 1
  return updated.filter((h, i) => i === lastIndex || h.key !== '' || h.value !== '')
}

export function McpServerFormModal({
  open,
  onOpenChange,
  mode,
  initialData,
  onSubmit,
  workspaceId,
  availableEnvVars,
  allowedMcpDomains,
}: McpServerFormModalProps) {
  const urlInputRef = useRef<HTMLInputElement>(null)

  const [formData, setFormData] = useState<McpServerFormData>(DEFAULT_FORM_DATA)
  const [originalData, setOriginalData] = useState<McpServerFormData>(DEFAULT_FORM_DATA)

  const [formMode, setFormMode] = useState<'form' | 'json'>('form')
  const [jsonInput, setJsonInput] = useState('')
  const [jsonError, setJsonError] = useState<string | null>(null)

  const [isSubmitting, setIsSubmitting] = useState(false)
  const [submitError, setSubmitError] = useState<string | null>(null)

  const { testResult, isTestingConnection, testConnection, clearTestResult } = useMcpServerTest()

  const [showEnvVars, setShowEnvVars] = useState(false)
  const [envSearchTerm, setEnvSearchTerm] = useState('')
  const [cursorPosition, setCursorPosition] = useState(0)
  const [activeInputField, setActiveInputField] = useState<InputFieldType | null>(null)
  const [activeHeaderIndex, setActiveHeaderIndex] = useState<number | null>(null)
  const [urlScrollLeft, setUrlScrollLeft] = useState(0)
  const [headerScrollLeft, setHeaderScrollLeft] = useState<Record<string, number>>({})

  const [showAdvanced, setShowAdvanced] = useState(false)
  const [oauthClientSecretTouched, setOauthClientSecretTouched] = useState(false)

  const [prevOpen, setPrevOpen] = useState(false)
  if (open && !prevOpen) {
    const data = initialData ?? DEFAULT_FORM_DATA
    setFormData(data)
    setOriginalData(structuredClone(data))
    setFormMode('form')
    setJsonInput('')
    setJsonError(null)
    setIsSubmitting(false)
    setSubmitError(null)
    setShowEnvVars(false)
    setActiveInputField(null)
    setActiveHeaderIndex(null)
    setUrlScrollLeft(0)
    setHeaderScrollLeft({})
    setShowAdvanced(!!(data.oauthClientId || data.oauthClientSecret || data.hasOauthClientSecret))
    setOauthClientSecretTouched(false)
  }
  if (open !== prevOpen) {
    setPrevOpen(open)
  }

  // Clear stale TanStack Query mutation state when the modal opens.
  // mutation.reset() is a side effect that can't be called during render.
  useEffect(() => {
    if (open) {
      clearTestResult()
    }
  }, [open, clearTestResult])

  const resetEnvVarState = () => {
    setShowEnvVars(false)
    setActiveInputField(null)
    setActiveHeaderIndex(null)
  }

  const handleInputChange = (field: InputFieldType, value: string, headerIndex?: number) => {
    const input = document.activeElement as HTMLInputElement
    const pos = input?.selectionStart || 0
    setCursorPosition(pos)

    if (testResult) clearTestResult()
    if (submitError) setSubmitError(null)

    const envVarTrigger = checkEnvVarTrigger(value, pos)
    setShowEnvVars(envVarTrigger.show)
    setEnvSearchTerm(envVarTrigger.show ? envVarTrigger.searchTerm : '')

    if (envVarTrigger.show) {
      setActiveInputField(field)
      setActiveHeaderIndex(headerIndex ?? null)
    } else {
      resetEnvVarState()
    }

    if (field === 'url') {
      setFormData((prev) => ({ ...prev, url: value }))
    } else if (headerIndex !== undefined) {
      const headerField = field === 'header-key' ? 'key' : 'value'
      setFormData((prev) => ({
        ...prev,
        headers: updateHeadersArray(prev.headers || [], headerIndex, headerField, value),
      }))
    }
  }

  const handleEnvVarSelect = (newValue: string) => {
    if (activeInputField === 'url') {
      setFormData((prev) => ({ ...prev, url: newValue }))
    } else if (activeHeaderIndex !== null) {
      const field = activeInputField === 'header-key' ? 'key' : 'value'
      const processedValue = field === 'key' ? newValue.replace(/[{}]/g, '') : newValue
      setFormData((prev) => ({
        ...prev,
        headers: updateHeadersArray(prev.headers || [], activeHeaderIndex, field, processedValue),
      }))
    }
    resetEnvVarState()
  }

  const handleHeaderScroll = (key: string, sl: number) => {
    setHeaderScrollLeft((prev) => ({ ...prev, [key]: sl }))
  }

  const isDomainBlocked =
    !!formData.url?.trim() && !isDomainAllowed(formData.url, allowedMcpDomains)
  const isFormValid = !!(formData.name.trim() && formData.url?.trim())
  const testButtonLabel = getTestButtonLabel(testResult, isTestingConnection)

  const computeHasChanges = (): boolean => {
    if (mode === 'add') return true
    if (formData.name !== originalData.name) return true
    if (formData.url !== originalData.url) return true
    if (formData.transport !== originalData.transport) return true
    if ((formData.oauthClientId || '') !== (originalData.oauthClientId || '')) return true
    if (oauthClientSecretTouched) return true
    const currentHeaders = formData.headers || []
    const origHeaders = originalData.headers || []
    if (currentHeaders.length !== origHeaders.length) return true
    for (let i = 0; i < currentHeaders.length; i++) {
      if (
        currentHeaders[i].key !== origHeaders[i].key ||
        currentHeaders[i].value !== origHeaders[i].value
      )
        return true
    }
    return false
  }
  const hasChanges = computeHasChanges()

  const parseJsonConfig = (
    json: string
  ): { name: string; url: string; headers: Record<string, string> } | null => {
    try {
      const parsed = JSON.parse(json)

      if (parsed.mcpServers && typeof parsed.mcpServers === 'object') {
        const entries = Object.entries(parsed.mcpServers)
        if (entries.length === 0) {
          setJsonError('No servers found in mcpServers')
          return null
        }
        if (entries.length > 1) {
          setJsonError(
            `Only the first server ("${entries[0][0]}") will be imported. Paste each config separately to add others.`
          )
        }
        const [name, config] = entries[0] as [string, Record<string, unknown>]
        if (!config.url || typeof config.url !== 'string') {
          setJsonError('Server config must include a "url" field')
          return null
        }
        if (entries.length <= 1) setJsonError(null)
        return { name, url: config.url, headers: extractStringHeaders(config.headers) }
      }

      if (parsed.url && typeof parsed.url === 'string') {
        setJsonError(null)
        return { name: '', url: parsed.url, headers: extractStringHeaders(parsed.headers) }
      }

      setJsonError('JSON must contain "mcpServers" or a "url" field')
      return null
    } catch {
      setJsonError('Invalid JSON')
      return null
    }
  }

  const handleTestConnection = async () => {
    if (!isFormValid) return

    await testConnection({
      name: formData.name,
      transport: formData.transport,
      url: formData.url!,
      headers: headersToRecord(formData.headers),
      timeout: formData.timeout,
      workspaceId,
    })
  }

  const handleSubmitForm = async () => {
    if (!isFormValid || isDomainBlocked) return

    setIsSubmitting(true)
    setSubmitError(null)
    try {
      const headers = headersToRecord(formData.headers)
      const oauthClientId = formData.oauthClientId?.trim()
      const oauthClientSecret = formData.oauthClientSecret?.trim()
      const originalClientId = (originalData.oauthClientId || '').trim()
      const oauthClientIdChanged = (oauthClientId || '') !== originalClientId

      const connectionResult = await testConnection({
        name: formData.name,
        transport: formData.transport,
        url: formData.url!,
        headers,
        timeout: formData.timeout,
        workspaceId,
      })

      if (!connectionResult.success && !connectionResult.authRequired) {
        setSubmitError(
          connectionResult.error || 'Connection test failed. Please check the URL and try again.'
        )
        return
      }

      await onSubmit({
        name: formData.name.trim(),
        transport: formData.transport,
        url: formData.url!,
        headers,
        timeout: formData.timeout || 30000,
        authType: connectionResult.authType,
        oauthClientId:
          mode === 'edit'
            ? oauthClientIdChanged
              ? (oauthClientId ?? '')
              : undefined
            : oauthClientId || undefined,
        oauthClientSecret:
          mode === 'edit'
            ? oauthClientSecretTouched
              ? (oauthClientSecret ?? '')
              : undefined
            : oauthClientSecret || undefined,
      })

      onOpenChange(false)
    } catch (error) {
      setSubmitError(getErrorMessage(error, 'Failed to save server'))
      logger.error('Failed to save MCP server:', error)
    } finally {
      setIsSubmitting(false)
    }
  }

  const handleSubmitJson = async () => {
    const config = parseJsonConfig(jsonInput)
    if (!config) return

    if (!config.name) {
      setJsonError('Server name is required. Use: { "mcpServers": { "name": { "url": "..." } } }')
      return
    }

    if (!isDomainAllowed(config.url, allowedMcpDomains)) {
      setJsonError('Domain not permitted by server policy')
      return
    }

    setIsSubmitting(true)
    setSubmitError(null)
    try {
      const connectionResult = await testConnection({
        name: config.name,
        transport: 'streamable-http',
        url: config.url,
        headers: config.headers,
        timeout: 30000,
        workspaceId,
      })

      if (!connectionResult.success && !connectionResult.authRequired) {
        setSubmitError(
          connectionResult.error || 'Connection test failed. Please check the URL and try again.'
        )
        return
      }

      await onSubmit({
        name: config.name,
        transport: 'streamable-http',
        url: config.url,
        headers: config.headers,
        timeout: 30000,
        authType: connectionResult.authType,
      })

      onOpenChange(false)
    } catch (error) {
      setSubmitError(getErrorMessage(error, 'Failed to save server'))
      logger.error('Failed to save MCP server from JSON:', error)
    } finally {
      setIsSubmitting(false)
    }
  }

  const isSubmitDisabled =
    isSubmitting || !isFormValid || isDomainBlocked || (mode === 'edit' && !hasChanges)

  const title = mode === 'add' ? 'Add New MCP Server' : 'Edit MCP Server'
  const submitLabel = mode === 'add' ? 'Add MCP' : 'Save'

  const handleToggleJsonMode = () => {
    if (testResult) clearTestResult()
    setFormMode(formMode === 'form' ? 'json' : 'form')
    setJsonError(null)
    setSubmitError(null)
  }

  const secondaryAction: ChipModalFooterAction | undefined =
    mode === 'add'
      ? {
          label: formMode === 'form' ? 'Edit JSON' : 'Edit Form',
          onClick: handleToggleJsonMode,
        }
      : formMode === 'form'
        ? {
            label: testButtonLabel,
            onClick: handleTestConnection,
            disabled: isTestingConnection || !isFormValid || isDomainBlocked,
          }
        : undefined

  const primaryAction: ChipModalFooterAction =
    formMode === 'json'
      ? {
          label: isSubmitting ? 'Adding...' : submitLabel,
          onClick: handleSubmitJson,
          disabled: isSubmitting || !jsonInput.trim(),
        }
      : {
          label: isSubmitting ? (mode === 'add' ? 'Adding...' : 'Saving...') : submitLabel,
          onClick: handleSubmitForm,
          disabled: isSubmitDisabled,
        }

  return (
    <ChipModal open={open} onOpenChange={onOpenChange} srTitle={title} size='lg'>
      <ChipModalHeader onClose={() => onOpenChange(false)}>{title}</ChipModalHeader>
      <ChipModalBody>
        {formMode === 'json' ? (
          <ChipModalField
            type='textarea'
            title='Configuration'
            value={jsonInput}
            onChange={(value) => {
              setJsonInput(value)
              if (jsonError) setJsonError(null)
              if (testResult) clearTestResult()
              if (submitError) setSubmitError(null)
            }}
            placeholder={`{\n  "mcpServers": {\n    "server-name": {\n      "url": "https://...",\n      "headers": {\n        "X-API-Key": "..."\n      }\n    }\n  }\n}`}
            minHeight={280}
            resizable
            error={jsonError}
          />
        ) : (
          <>
            <input
              type='text'
              name='fakeusernameremembered'
              autoComplete='username'
              style={{ position: 'absolute', left: '-9999px', opacity: 0, pointerEvents: 'none' }}
              tabIndex={-1}
              readOnly
            />
            <input
              type='password'
              name='fakepasswordremembered'
              autoComplete='current-password'
              style={{ position: 'absolute', left: '-9999px', opacity: 0, pointerEvents: 'none' }}
              tabIndex={-1}
              readOnly
            />
            <ChipModalField
              type='input'
              title='Server Name'
              value={formData.name}
              onChange={(value) => {
                if (testResult) clearTestResult()
                if (submitError) setSubmitError(null)
                setFormData((prev) => ({ ...prev, name: value }))
              }}
              placeholder='e.g., My MCP Server'
            />

            <ChipModalField
              type='custom'
              title='Server URL'
              error={isDomainBlocked ? 'Domain not permitted by server policy' : undefined}
            >
              <FormattedInput
                ref={urlInputRef}
                placeholder='https://mcp.server.dev/{{YOUR_API_KEY}}/sse'
                value={formData.url || ''}
                scrollLeft={urlScrollLeft}
                showEnvVars={showEnvVars && activeInputField === 'url'}
                envVarProps={{
                  searchTerm: envSearchTerm,
                  cursorPosition,
                  workspaceId,
                  onSelect: handleEnvVarSelect,
                  onClose: resetEnvVarState,
                }}
                availableEnvVars={availableEnvVars}
                onChange={(e) => handleInputChange('url', e.target.value)}
                onScroll={setUrlScrollLeft}
              />
            </ChipModalField>

            <ChipModalField type='custom' title='Headers'>
              <div className='flex max-h-[140px] flex-col gap-2 overflow-y-auto'>
                {(formData.headers || []).map((header, index) => (
                  <HeaderRow
                    key={index}
                    header={header}
                    index={index}
                    headerScrollLeft={headerScrollLeft}
                    showEnvVars={showEnvVars}
                    activeInputField={activeInputField}
                    activeHeaderIndex={activeHeaderIndex}
                    envSearchTerm={envSearchTerm}
                    cursorPosition={cursorPosition}
                    workspaceId={workspaceId}
                    availableEnvVars={availableEnvVars}
                    onInputChange={handleInputChange}
                    onHeaderScroll={handleHeaderScroll}
                    onEnvVarSelect={handleEnvVarSelect}
                    onEnvVarClose={resetEnvVarState}
                  />
                ))}
              </div>
            </ChipModalField>

            <Button
              type='button'
              variant='ghost'
              onClick={() => setShowAdvanced((v) => !v)}
              className='gap-1 self-start px-2 py-0 text-small'
            >
              {showAdvanced ? (
                <ChevronDown className='size-[14px]' />
              ) : (
                <ChevronRight className='size-[14px]' />
              )}
              Advanced settings
            </Button>
            {showAdvanced && (
              <>
                <ChipModalField type='custom' title='Client ID'>
                  <ChipInput
                    placeholder='OAuth Client ID (optional)'
                    value={formData.oauthClientId || ''}
                    name='mcp_oauth_client_id'
                    autoComplete='off'
                    autoCorrect='off'
                    autoCapitalize='off'
                    data-lpignore='true'
                    data-form-type='other'
                    onChange={(e) => {
                      if (testResult) clearTestResult()
                      if (submitError) setSubmitError(null)
                      setFormData((prev) => ({ ...prev, oauthClientId: e.target.value }))
                    }}
                  />
                </ChipModalField>
                <ChipModalField
                  type='custom'
                  title='Client Secret'
                  hint="Only needed for servers that don't support automatic client registration."
                >
                  <SecretInput
                    placeholder='OAuth Client Secret (optional)'
                    value={formData.oauthClientSecret || ''}
                    name='mcp_oauth_client_secret'
                    autoComplete='new-password'
                    autoCorrect='off'
                    autoCapitalize='off'
                    data-lpignore='true'
                    data-form-type='other'
                    onChange={(value) => {
                      if (testResult) clearTestResult()
                      if (submitError) setSubmitError(null)
                      setOauthClientSecretTouched(value.length > 0)
                      setFormData((prev) => ({ ...prev, oauthClientSecret: value }))
                    }}
                  />
                </ChipModalField>
              </>
            )}
          </>
        )}
        <ChipModalError>{submitError}</ChipModalError>
      </ChipModalBody>
      <ChipModalFooter
        onCancel={() => onOpenChange(false)}
        secondaryActions={secondaryAction ? [secondaryAction] : undefined}
        primaryAction={primaryAction}
      />
    </ChipModal>
  )
}
