import { useState, useRef, useEffect, useMemo, useImperativeHandle, forwardRef, memo, useCallback, type KeyboardEvent, type PointerEvent as ReactPointerEvent } from 'react'
import { useSendPrompt, useAbortSession, useSendShell, useAgents } from '@/hooks/useOpenCode'
import { useCommands } from '@/hooks/useCommands'
import { useCommandHandler } from '@/hooks/useCommandHandler'
import { useFileSearch } from '@/hooks/useFileSearch'
import { useModelSelection } from '@/hooks/useModelSelection'
import { useOpenCodeClient } from '@/hooks/useOpenCode'
import { useVariants } from '@/hooks/useVariants'
import { useSessionAgent } from '@/hooks/useSessionAgent'
import { useSTT } from '@/hooks/useSTT'

import { useUserBash } from '@/stores/userBashStore'
import { useModelStore } from '@/stores/modelStore'
import { useSessionAgentStore } from '@/stores/sessionAgentStore'
import { useUIState } from '@/stores/uiStateStore'
import { useMobile } from '@/hooks/useMobile'

import { usePermissions } from '@/contexts/EventContext'
import { ArrowDown, Upload, X, Mic, MicOff } from 'lucide-react'

import { SquareFill } from '@/components/ui/square-fill'

import { CommandSuggestions } from '@/components/command/CommandSuggestions'
import { MentionSuggestions, type MentionItem } from './MentionSuggestions'
import { SessionStatusIndicator } from '@/components/ui/session-status-indicator'
import { ModelQuickSelect } from '@/components/model/ModelQuickSelect'
import { AgentQuickSelect } from '@/components/agent/AgentQuickSelect'
import { VoiceStatusOverlay, type VoiceStatusOverlayState } from './VoiceStatusOverlay'
import { detectMentionTrigger, parsePromptToParts, getFilename, filterAgentsByQuery } from '@/lib/promptParser'
import { formatModelName, getProviders } from '@/api/providers'
import { useQuery } from '@tanstack/react-query'


import type { components } from '@/api/opencode-types'
import type { FileAttachmentInfo, ImageAttachment } from '@/api/types'

const ACCEPTED_IMAGE_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp", "image/heic", "image/heif"]


const revokeBlobUrls = (attachments: ImageAttachment[]) => {
  attachments.forEach((attachment) => {
    if (attachment.dataUrl.startsWith('blob:')) {
      URL.revokeObjectURL(attachment.dataUrl)
    }
  })
}

const ACCEPTED_FILE_TYPES = [...ACCEPTED_IMAGE_TYPES, "application/pdf"]

const VOICE_SEND_SWIPE_THRESHOLD = 30
type VoiceButtonVariant = 'desktop' | 'mobile'


type CommandType = components['schemas']['Command']

export interface PromptInputHandle {
  setPromptValue: (value: string) => void
  clearPrompt: () => void
  triggerFileUpload: () => void
}

interface PromptInputProps {
  opcodeUrl: string
  directory?: string
  sessionID: string
  disabled?: boolean
  showScrollButton?: boolean
  isSessionActive?: boolean
  isStreamingResponse?: boolean
  onScrollToBottom?: () => void
  onShowSessionsDialog?: () => void
  onShowModelsDialog?: () => void
  onShowHelpDialog?: () => void
  onToggleDetails?: () => boolean
  onExportSession?: () => void
  onPromptChange?: (hasContent: boolean) => void
}

export const PromptInput = memo(forwardRef<PromptInputHandle, PromptInputProps>(function PromptInput({ 
  opcodeUrl,
  directory,
  sessionID,
  disabled,
  showScrollButton,
  isSessionActive = false,
  isStreamingResponse = false,
  onScrollToBottom,
  onShowSessionsDialog,
  onShowModelsDialog,
  onShowHelpDialog,
  onToggleDetails,
  onExportSession,
  onPromptChange
}, ref) {
  const [prompt, setPrompt] = useState('')
  const [isBashMode, setIsBashMode] = useState(false)
  const [showSuggestions, setShowSuggestions] = useState(false)
  const [suggestionQuery, setSuggestionQuery] = useState('')
  const [attachedFiles, setAttachedFiles] = useState(new Map<string, FileAttachmentInfo>())
  const [imageAttachments, setImageAttachments] = useState<ImageAttachment[]>([])
  const [isDragging, setIsDragging] = useState(false)
  const [showMentionSuggestions, setShowMentionSuggestions] = useState(false)
  const [mentionQuery, setMentionQuery] = useState('')
  const [mentionRange, setMentionRange] = useState<{ start: number, end: number } | null>(null)
  const [selectedMentionIndex, setSelectedMentionIndex] = useState(0)
  const [selectedCommandIndex, setSelectedCommandIndex] = useState(0)
  const [selectedAgent, setSelectedAgent] = useState<string | null>(null)
  const [localMode, setLocalMode] = useState<string | null>(null)
  const [isFocused, setIsFocused] = useState(false)
  const [isTogglingRecording, setIsTogglingRecording] = useState(false)
  const [isVoiceSwipeArmed, setIsVoiceSwipeArmed] = useState(false)
  const [isVoiceAutoSendPending, setIsVoiceAutoSendPending] = useState(false)
  const [isVoiceAutoSendWaitingForTranscript, setIsVoiceAutoSendWaitingForTranscript] = useState(false)
  const lastAddedTranscriptRef = useRef('')
  const voiceGestureStartYRef = useRef<number | null>(null)
  const voiceSwipeArmedRef = useRef(false)
  const pendingVoiceAutoSubmitRef = useRef(false)
  const ignoreVoiceClickUntilRef = useRef(0)
  const voiceStartRequestRef = useRef(0)
  const handleSubmitRef = useRef<() => void>(() => {})
  const pendingPromptCommand = useUIState((state) => state.pendingPromptCommand)
  const pendingPromptFile = useUIState((state) => state.pendingPromptFile)
  const clearPendingPromptCommand = useUIState((state) => state.clearPendingPromptCommand)
  const clearPendingPromptFile = useUIState((state) => state.clearPendingPromptFile)

  const {
    isRecording,
    isProcessing,
    startRecording,
    stopRecording,
    abortRecording,
    isSupported: sttSupported,
    isEnabled: sttEnabled,
    interimTranscript,
    transcript,
    clear: clearSTT,
  } = useSTT()

  const textareaRef = useRef<HTMLTextAreaElement>(null)
  const fileInputRef = useRef<HTMLInputElement>(null)
  const voiceButtonContainerRef = useRef<HTMLDivElement | null>(null)

  const resetVoiceGestureState = useCallback(() => {
    voiceGestureStartYRef.current = null
    voiceSwipeArmedRef.current = false
    pendingVoiceAutoSubmitRef.current = false
    ignoreVoiceClickUntilRef.current = 0
    setIsVoiceSwipeArmed(false)
    setIsVoiceAutoSendPending(false)
    setIsVoiceAutoSendWaitingForTranscript(false)
  }, [])
  
  useImperativeHandle(ref, () => ({
    setPromptValue: (value: string) => {
      setPrompt(value)
      textareaRef.current?.focus()
    },
    clearPrompt: () => {
      setPrompt('')
      setAttachedFiles(new Map())
      revokeBlobUrls(imageAttachments)
      setImageAttachments([])
      setSelectedAgent(null)
      resetVoiceGestureState()
      if (isRecording) {
        abortRecording()
      } else {
        clearSTT()
      }
      textareaRef.current?.focus()
    },
    triggerFileUpload: () => {
      fileInputRef.current?.click()
    }
  }), [imageAttachments, clearSTT, isRecording, abortRecording, resetVoiceGestureState])
  const sendPrompt = useSendPrompt(opcodeUrl, directory)
  const sendShell = useSendShell(opcodeUrl, directory)
  const abortSession = useAbortSession(opcodeUrl, directory, sessionID)
  const { filterCommands } = useCommands(opcodeUrl)
  const { executeCommand } = useCommandHandler({
    opcodeUrl,
    sessionID,
    directory,
    onShowSessionsDialog,
    onShowModelsDialog,
    onShowHelpDialog,
    onToggleDetails,
    onExportSession,
    currentAgent: localMode || undefined
  })
  
  const { files: searchResults } = useFileSearch(
    opcodeUrl,
    mentionQuery,
    showMentionSuggestions,
    directory
  )
  
  const { data: agents = [] } = useAgents(opcodeUrl, directory)
  
  const mentionItems = useMemo((): MentionItem[] => {
    const filteredAgents = filterAgentsByQuery(
      agents.map(a => ({ name: a.name, description: a.description })),
      mentionQuery
    )
    
    const agentItems: MentionItem[] = filteredAgents.map(agent => ({
      type: 'agent',
      value: agent.name,
      label: agent.name,
      description: agent.description
    }))
    
    const fileItems: MentionItem[] = searchResults.map(file => ({
      type: 'file',
      value: file,
      label: getFilename(file),
      description: file
    }))
    
    return [...agentItems, ...fileItems]
  }, [agents, searchResults, mentionQuery])
  

  const addUserBashCommand = useUserBash((s) => s.addUserBashCommand)

  const handleSubmit = () => {
    if (disabled) return
    if (!prompt.trim() && imageAttachments.length === 0) return

    pendingVoiceAutoSubmitRef.current = false
    setIsVoiceAutoSendPending(false)

    if (isStreamingResponse) {
      const parts = parsePromptToParts(prompt, attachedFiles, imageAttachments)
      const agentUsed = selectedAgent || currentMode
      sendPrompt.mutate({
        sessionID,
        parts,
        model: currentModel,
        agent: agentUsed,
        variant: currentVariant,
        queued: true
      })
      setStoredAgent(sessionID, agentUsed)
      if (model) {
        setStoredModel({ providerID: model.providerID, modelID: model.modelID })
      }
      setPrompt('')
      setAttachedFiles(new Map())
      revokeBlobUrls(imageAttachments)
      setImageAttachments([])
      setSelectedAgent(null)
      clearSTT()
      return
    }

    if (isBashMode) {
      const command = prompt.startsWith('!') ? prompt.slice(1) : prompt
      addUserBashCommand(command)
      sendShell.mutate({
        sessionID,
        command,
        agent: currentMode
      })
      setStoredAgent(sessionID, currentMode)
      setPrompt('')
      setIsBashMode(false)
      clearSTT()
      return
    }

    

    const commandMatch = prompt.match(/^\/([a-zA-Z0-9_-]+)(?:\s+(.*))?$/)
    if (commandMatch) {
      const [, commandName, commandArgs] = commandMatch
      const command = filterCommands(commandName)[0]
      
      if (command) {
        executeCommand(command, commandArgs?.trim() || '')
        setPrompt('')
        clearSTT()
        return
      }
    }

    const parts = parsePromptToParts(prompt, attachedFiles, imageAttachments)
    const agentUsed = selectedAgent || currentMode

    sendPrompt.mutate({
      sessionID,
      parts,
      model: currentModel,
      agent: agentUsed,
      variant: currentVariant
    })

    setStoredAgent(sessionID, agentUsed)
    if (model) {
      setStoredModel({ providerID: model.providerID, modelID: model.modelID })
    }
    setPrompt('')
    setAttachedFiles(new Map())
    revokeBlobUrls(imageAttachments)
    setImageAttachments([])
    setSelectedAgent(null)
    clearSTT()
  }

  handleSubmitRef.current = handleSubmit

  const handleStop = () => {
    abortSession.mutate(sessionID)
  }

  const handleCommandSelect = useCallback(async (command: CommandType) => {
    if (!textareaRef.current) return
    
    setShowSuggestions(false)
    setSuggestionQuery('')
    
    if (command.template) {
      const cleanedTemplate = command.template
        .replace(/\$ARGUMENTS/g, '')
        .replace(/\$\d+/g, '')
        .trim()
      
      setPrompt(cleanedTemplate)
      
      setTimeout(() => {
        if (textareaRef.current) {
          textareaRef.current.focus()
          textareaRef.current.setSelectionRange(cleanedTemplate.length, cleanedTemplate.length)
          textareaRef.current.scrollTop = textareaRef.current.scrollHeight
        }
      }, 0)
    } else {
      const cursorPosition = textareaRef.current.selectionStart
      const commandMatch = prompt.slice(0, cursorPosition).match(/(^|\s)\/([a-zA-Z0-9_-]*)$/)
      
      const beforeCommand = commandMatch ? prompt.slice(0, commandMatch.index) : ''
      const afterCommand = commandMatch ? prompt.slice(cursorPosition) : ''
      const newPrompt = beforeCommand + '/' + command.name + ' ' + afterCommand
      
      setPrompt(newPrompt)
      
      setTimeout(() => {
        if (textareaRef.current) {
          const newCursorPos = beforeCommand.length + command.name.length + 2
          textareaRef.current.focus()
          textareaRef.current.setSelectionRange(newCursorPos, newCursorPos)
          textareaRef.current.scrollTop = textareaRef.current.scrollHeight
        }
      }, 0)
    }
  }, [prompt])

  useEffect(() => {
    if (!pendingPromptCommand) return
    handleCommandSelect(pendingPromptCommand.command)
    clearPendingPromptCommand()
  }, [pendingPromptCommand, handleCommandSelect, clearPendingPromptCommand])

  const insertFileMention = useCallback((filePath: string, range: { start: number, end: number } | null = mentionRange) => {
    const filename = getFilename(filePath)
    const beforeMention = range ? prompt.slice(0, range.start) : `${prompt}${prompt.trim() ? ' ' : ''}`
    const afterMention = range ? prompt.slice(range.end) : ''
    const newPrompt = beforeMention + '@' + filename + ' ' + afterMention

    setPrompt(newPrompt)

    const absolutePath = filePath.startsWith('/')
      ? filePath
      : directory
        ? `${directory}/${filePath}`
        : filePath

    setAttachedFiles(prev => {
      const next = new Map(prev)
      next.set(filename.toLowerCase(), {
        path: absolutePath,
        name: filename
      })
      return next
    })

    setTimeout(() => {
      if (textareaRef.current) {
        const newCursorPos = beforeMention.length + filename.length + 2
        textareaRef.current.focus()
        textareaRef.current.setSelectionRange(newCursorPos, newCursorPos)
        textareaRef.current.scrollTop = textareaRef.current.scrollHeight
      }
    }, 0)
  }, [directory, mentionRange, prompt])

  useEffect(() => {
    if (!pendingPromptFile) return
    insertFileMention(pendingPromptFile.path, null)
    clearPendingPromptFile()
  }, [pendingPromptFile, insertFileMention, clearPendingPromptFile])

  const handleMentionSelect = (item: MentionItem) => {
    if (!mentionRange || !textareaRef.current) return
    
    const beforeMention = prompt.slice(0, mentionRange.start)
    const afterMention = prompt.slice(mentionRange.end)
    
    if (item.type === 'agent') {
      const newPrompt = beforeMention + '@' + item.value + ' ' + afterMention
      setPrompt(newPrompt)
      setSelectedAgent(item.value)
      
      setTimeout(() => {
        if (textareaRef.current) {
          const newCursorPos = beforeMention.length + item.value.length + 2
          textareaRef.current.focus()
          textareaRef.current.setSelectionRange(newCursorPos, newCursorPos)
          textareaRef.current.scrollTop = textareaRef.current.scrollHeight
        }
      }, 0)
    } else {
      insertFileMention(item.value, mentionRange)
    }
    
    setShowMentionSuggestions(false)
    setMentionQuery('')
    setMentionRange(null)
  }

  const handleAgentChange = (agentName: string) => {
    setLocalMode(agentName)
    setStoredAgent(sessionID, agentName)
    const agent = agents.find(a => a.name === agentName)
    if (agent?.model) {
      setStoredModel({ providerID: agent.model.providerID, modelID: agent.model.modelID })
    }
  }

  const startVoiceRecording = async () => {
    if (isRecording || isProcessing || isTogglingRecording) {
      return
    }

    const startRequestId = voiceStartRequestRef.current + 1
    voiceStartRequestRef.current = startRequestId
    pendingVoiceAutoSubmitRef.current = false
    setIsVoiceAutoSendPending(false)
    setIsVoiceAutoSendWaitingForTranscript(false)
    setIsVoiceSwipeArmed(false)
    voiceSwipeArmedRef.current = false
    setIsTogglingRecording(true)

    const started = await startRecording()
    if (voiceStartRequestRef.current !== startRequestId) {
      setIsTogglingRecording(false)
      if (started) {
        abortRecording()
      }
      return
    }

    if (!started) {
      setIsTogglingRecording(false)
      return
    }

    if (typeof navigator !== 'undefined' && navigator.vibrate) {
      navigator.vibrate(10)
    }
    textareaRef.current?.blur()
  }

  const handleVoiceClick = async () => {
    if (Date.now() < ignoreVoiceClickUntilRef.current) {
      return
    }

    if (isRecording) {
      pendingVoiceAutoSubmitRef.current = false
      setIsVoiceAutoSendPending(false)
      setIsVoiceAutoSendWaitingForTranscript(false)
      setIsVoiceSwipeArmed(false)
      voiceSwipeArmedRef.current = false
      stopRecording()
      return
    }

    await startVoiceRecording()
  }

  const handleVoicePointerDown = (event: ReactPointerEvent<HTMLDivElement>) => {
    if ((event.pointerType === 'mouse' && event.button !== 0) || disabled || isProcessing || !isRecording) {
      return
    }

    voiceGestureStartYRef.current = event.clientY
    voiceSwipeArmedRef.current = false
    setIsVoiceSwipeArmed(false)

    if (event.currentTarget.setPointerCapture) {
      event.currentTarget.setPointerCapture(event.pointerId)
    }
  }

  const handleVoicePointerMove = (event: ReactPointerEvent<HTMLDivElement>) => {
    if (!isRecording || voiceGestureStartYRef.current === null) {
      return
    }

    const deltaY = voiceGestureStartYRef.current - event.clientY
    const nextIsSwipeArmed = deltaY >= VOICE_SEND_SWIPE_THRESHOLD

    if (nextIsSwipeArmed !== voiceSwipeArmedRef.current) {
      voiceSwipeArmedRef.current = nextIsSwipeArmed
      setIsVoiceSwipeArmed(nextIsSwipeArmed)

      if (nextIsSwipeArmed && typeof navigator !== 'undefined' && navigator.vibrate) {
        navigator.vibrate(10)
      }
    }
  }

  const handleVoicePointerEnd = (event: ReactPointerEvent<HTMLDivElement>, canceled = false) => {
    if (event.currentTarget.releasePointerCapture && event.currentTarget.hasPointerCapture(event.pointerId)) {
      event.currentTarget.releasePointerCapture(event.pointerId)
    }

    if (voiceGestureStartYRef.current === null) {
      return
    }

    voiceGestureStartYRef.current = null

    if (canceled || !voiceSwipeArmedRef.current) {
      ignoreVoiceClickUntilRef.current = Date.now() + 400
      voiceSwipeArmedRef.current = false
      setIsVoiceSwipeArmed(false)
      pendingVoiceAutoSubmitRef.current = false
      setIsVoiceAutoSendPending(false)
      setIsVoiceAutoSendWaitingForTranscript(false)
      stopRecording()
      return
    }

    ignoreVoiceClickUntilRef.current = Date.now() + 400
    voiceSwipeArmedRef.current = false
    setIsVoiceSwipeArmed(false)
    pendingVoiceAutoSubmitRef.current = true
    setIsVoiceAutoSendPending(true)
    setIsVoiceAutoSendWaitingForTranscript(true)

    if (typeof navigator !== 'undefined' && navigator.vibrate) {
      navigator.vibrate([10, 20, 10])
    }

    stopRecording()
  }

  const cancelVoiceInput = useCallback(() => {
    voiceStartRequestRef.current += 1
    resetVoiceGestureState()
    setIsTogglingRecording(false)

    if (isRecording || isTogglingRecording || isProcessing) {
      abortRecording()
    }
  }, [abortRecording, isProcessing, isRecording, isTogglingRecording, resetVoiceGestureState])

  useEffect(() => {
    const isVoiceFeedbackVisible = isRecording || isTogglingRecording || isProcessing || isVoiceAutoSendPending

    if (!isVoiceFeedbackVisible) {
      return
    }

    const handleOutsidePointerDown = (event: PointerEvent) => {
      const target = event.target

      if (!(target instanceof Node)) {
        return
      }

      if (voiceButtonContainerRef.current?.contains(target)) {
        return
      }

      if (isRecording) {
        voiceGestureStartYRef.current = null
        voiceSwipeArmedRef.current = false
        pendingVoiceAutoSubmitRef.current = false
        setIsVoiceSwipeArmed(false)
        setIsVoiceAutoSendPending(false)
        setIsVoiceAutoSendWaitingForTranscript(false)
        stopRecording()
        return
      }

      if (isProcessing || isVoiceAutoSendPending) {
        pendingVoiceAutoSubmitRef.current = false
        setIsVoiceAutoSendPending(false)
        setIsVoiceAutoSendWaitingForTranscript(false)
        return
      }

      cancelVoiceInput()
    }

    document.addEventListener('pointerdown', handleOutsidePointerDown, true)

    return () => {
      document.removeEventListener('pointerdown', handleOutsidePointerDown, true)
    }
  }, [cancelVoiceInput, isProcessing, isRecording, isTogglingRecording, isVoiceAutoSendPending, stopRecording])

  useEffect(() => {
    const textToUse = transcript || interimTranscript
    if (!isRecording && textToUse && textToUse !== 'Processing...' && textToUse !== 'Recording...') {
      const trimmedTranscript = textToUse.trim()
      if (trimmedTranscript && trimmedTranscript !== lastAddedTranscriptRef.current) {
        if (prompt === '' || prompt === 'Processing...' || prompt === 'Recording...') {
          setPrompt(trimmedTranscript)
        } else {
          setPrompt(prev => `${prev} ${trimmedTranscript}`)
        }
        lastAddedTranscriptRef.current = trimmedTranscript

        if (!pendingVoiceAutoSubmitRef.current) {
          textareaRef.current?.focus()
        } else {
          setIsVoiceAutoSendWaitingForTranscript(false)
        }
      }
    }
  }, [isRecording, interimTranscript, transcript, prompt])

  useEffect(() => {
    if (isRecording && isTogglingRecording) {
      setIsTogglingRecording(false)
    }
  }, [isRecording, isTogglingRecording])

  useEffect(() => {
    if (isTogglingRecording) {
      lastAddedTranscriptRef.current = ''
    }
  }, [isTogglingRecording])

  useEffect(() => {
    if (!pendingVoiceAutoSubmitRef.current || isVoiceAutoSendWaitingForTranscript || isRecording || isProcessing || !prompt.trim()) {
      return
    }

    pendingVoiceAutoSubmitRef.current = false
    setIsVoiceAutoSendPending(false)
    setIsVoiceAutoSendWaitingForTranscript(false)
    handleSubmitRef.current()
  }, [prompt, isRecording, isProcessing, isVoiceAutoSendWaitingForTranscript])

  useEffect(() => {
    if (pendingVoiceAutoSubmitRef.current && !isRecording && !isProcessing && !transcript.trim() && !interimTranscript.trim()) {
      pendingVoiceAutoSubmitRef.current = false
      setIsVoiceAutoSendPending(false)
      setIsVoiceAutoSendWaitingForTranscript(false)
    }
  }, [isRecording, isProcessing, transcript, interimTranscript])

  const addImageAttachment = (file: File) => {
    const generateId = () => {
      if (typeof crypto !== 'undefined' && crypto.randomUUID) {
        return crypto.randomUUID()
      }
      return `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`
    }

    try {
      const reader = new FileReader()
      
      reader.onloadend = () => {
        try {
          if (reader.readyState !== 2) return
          
          const dataUrl = reader.result as string
          
          if (!dataUrl) {
            const blobUrl = URL.createObjectURL(file)
            const attachment: ImageAttachment = {
              id: generateId(),
              filename: file.name,
              mime: file.type || 'image/png',
              dataUrl: blobUrl,
            }
            setImageAttachments((prev) => [...prev, attachment])
            return
          }
          
          const attachment: ImageAttachment = {
            id: generateId(),
            filename: file.name,
            mime: file.type || 'image/png',
            dataUrl,
          }
          setImageAttachments((prev) => [...prev, attachment])
        } catch (innerError) {
          console.error('Error inside onloadend:', innerError)
        }
      }
      
      reader.onerror = () => {
        console.error('FileReader error:', reader.error?.message)
      }
      
      reader.readAsDataURL(file)
    } catch (error) {
      console.error('Error reading file:', error)
    }
  }

  const removeImageAttachment = (id: string) => {
    setImageAttachments((prev) => {
      const attachment = prev.find((a) => a.id === id)
      if (attachment?.dataUrl.startsWith('blob:')) {
        URL.revokeObjectURL(attachment.dataUrl)
      }
      return prev.filter((a) => a.id !== id)
    })
  }

  const handlePaste = async (event: React.ClipboardEvent<HTMLTextAreaElement>) => {
    const clipboardData = event.clipboardData
    if (!clipboardData) return

    const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !(window as { MSStream?: boolean }).MSStream
    const isSecureContext = window.isSecureContext || (window.location.protocol === 'http:' && window.location.hostname === 'localhost')

if (isIOS && isSecureContext && navigator.clipboard && navigator.clipboard.read) {
      try {
        const text = await navigator.clipboard.readText()
        if (text && text.trim()) {
          return
        }
      } catch {
      }

      event.preventDefault()

      try {
        const clipboardItems = await navigator.clipboard.read()

        for (const item of clipboardItems) {
          for (const type of item.types) {
            if (ACCEPTED_FILE_TYPES.includes(type) || type.startsWith('image/')) {
              try {
                const blob = await item.getType(type)
                const file = new File([blob], `pasted-${Date.now()}.${type.split('/')[1]}`, { type })
                addImageAttachment(file)
              } catch (err) {
                console.error('Failed to read clipboard item type:', err)
              }
            }
          }
        }
        return
      } catch (error) {
        console.error('Clipboard read failed on iOS:', error)
      }
    }

    const items = Array.from(clipboardData.items)
    
    const imageItems = items.filter((item) => {
      if (item.kind !== 'file') return false
      
      const hasKnownType = ACCEPTED_FILE_TYPES.includes(item.type)
      const isLikelyImage = item.type.startsWith('image/')
      const hasNoType = !item.type || item.type === ''
      
      return hasKnownType || isLikelyImage || hasNoType
    })

    if (imageItems.length > 0) {
      event.preventDefault()
      for (const item of imageItems) {
        const file = item.getAsFile()
        if (file) {
          const isValidImageFile = 
            ACCEPTED_FILE_TYPES.includes(file.type) ||
            file.type.startsWith('image/') ||
            file.size > 0
          
          if (isValidImageFile) {
            addImageAttachment(file)
          }
        }
      }
    }
  }

  const handleDragOver = (event: React.DragEvent<HTMLTextAreaElement>) => {
    event.preventDefault()
    event.stopPropagation()
    if (event.dataTransfer?.types.includes('Files')) {
      setIsDragging(true)
    }
  }

  const handleDragLeave = (event: React.DragEvent<HTMLTextAreaElement>) => {
    event.preventDefault()
    event.stopPropagation()
    setIsDragging(false)
  }

  const handleDrop = async (event: React.DragEvent<HTMLTextAreaElement>) => {
    event.preventDefault()
    event.stopPropagation()
    setIsDragging(false)

    const files = event.dataTransfer?.files
    if (files) {
      for (const file of Array.from(files)) {
        if (ACCEPTED_FILE_TYPES.includes(file.type)) {
          addImageAttachment(file)
        }
      }
    }
  }

  const handleFileInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    const file = event.currentTarget.files?.[0]
    if (file && ACCEPTED_FILE_TYPES.includes(file.type)) {
      addImageAttachment(file)
    }
    event.currentTarget.value = ''
  }

  const handleKeyDown = (e: KeyboardEvent<HTMLTextAreaElement>) => {
    if (isBashMode && e.key === 'Escape') {
      e.preventDefault()
      setIsBashMode(false)
      setPrompt('')
      return
    }

    if (showMentionSuggestions && mentionItems.length > 0) {
      if (e.key === 'ArrowDown') {
        e.preventDefault()
        setSelectedMentionIndex(prev => 
          prev < mentionItems.length - 1 ? prev + 1 : prev
        )
        return
      }
      
      if (e.key === 'ArrowUp') {
        e.preventDefault()
        setSelectedMentionIndex(prev => prev > 0 ? prev - 1 : 0)
        return
      }
      
      if (e.key === 'Enter') {
        e.preventDefault()
        if (mentionItems[selectedMentionIndex]) {
          handleMentionSelect(mentionItems[selectedMentionIndex])
        }
        return
      }
      
      if (e.key === 'Escape') {
        e.preventDefault()
        setShowMentionSuggestions(false)
        setMentionQuery('')
        setMentionRange(null)
        return
      }
    }
    
    if (showSuggestions) {
      const filteredCommands = filterCommands(suggestionQuery)
      
      if (e.key === 'ArrowDown') {
        e.preventDefault()
        setSelectedCommandIndex(prev => (prev + 1) % filteredCommands.length)
        return
      }
      
      if (e.key === 'ArrowUp') {
        e.preventDefault()
        setSelectedCommandIndex(prev => (prev - 1 + filteredCommands.length) % filteredCommands.length)
        return
      }
      
      if (e.key === 'Enter') {
        e.preventDefault()
        const selectedCommand = filteredCommands[selectedCommandIndex]
        if (selectedCommand) {
          handleCommandSelect(selectedCommand)
        }
        return
      }
      
      if (e.key === 'Escape') {
        e.preventDefault()
        setShowSuggestions(false)
        setSuggestionQuery('')
        setSelectedCommandIndex(0)
        return
      }
    }
    
    if (e.key === 'Enter' && (e.metaKey || e.ctrlKey || (isMobile && !e.shiftKey))) {
      e.preventDefault()
      if (isMobile) {
        textareaRef.current?.blur()
      }
      handleSubmit()
    } else if (e.key === 'Escape') {
      setShowSuggestions(false)
      setSuggestionQuery('')
      setShowMentionSuggestions(false)
      setMentionQuery('')
      setMentionRange(null)
      setPrompt('')
      revokeBlobUrls(imageAttachments)
      setImageAttachments([])
      resetVoiceGestureState()
      clearSTT()
    } else if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 't') {
      e.preventDefault()
      if (hasVariants) {
        cycleVariant()
      }
    }
  }

  const handleInput = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
    const value = e.target.value
    
    if (value === '!' && prompt === '') {
      setIsBashMode(true)
      setPrompt(value)
      return
    }
    
    if (isBashMode && value === '') {
      setIsBashMode(false)
    }
    
    setPrompt(value)

    if (isBashMode) {
      return
    }

    const cursorPosition = e.target.selectionStart
    
    const mentionTrigger = detectMentionTrigger(value, cursorPosition)
    
    if (mentionTrigger) {
      setMentionQuery(mentionTrigger.query)
      setMentionRange({ start: mentionTrigger.start, end: mentionTrigger.end })
      setShowMentionSuggestions(true)
      setSelectedMentionIndex(0)
    } else {
      const commandMatch = value.slice(0, cursorPosition).match(/(^|\s)\/([a-zA-Z0-9_-]*)$/)
      
      if (commandMatch) {
        const query = commandMatch[2]
        setSuggestionQuery(query)
        setShowSuggestions(true)
        setSelectedCommandIndex(0)
      } else {
        setShowSuggestions(false)
        setSuggestionQuery('')
      }
      
      if (showMentionSuggestions) {
        setShowMentionSuggestions(false)
        setMentionQuery('')
        setMentionRange(null)
      }
    }
  }

  const sessionAgent = useSessionAgent(opcodeUrl, sessionID, directory)
  const currentMode = localMode ?? sessionAgent.agent
  const setStoredAgent = useSessionAgentStore((s) => s.setAgent)
  const syncedSessionModelRef = useRef<string | undefined>(undefined)

  const client = useOpenCodeClient(opcodeUrl, directory)
  const { data: providersData } = useQuery({
    queryKey: ['opencode', 'providers', opcodeUrl, directory],
    queryFn: () => getProviders(directory),
    enabled: !!client,
    staleTime: 30000,
  })

  const { model, modelString, setModel: setStoredModel, setActiveModel } = useModelSelection(opcodeUrl, directory)
  const setStoreVariant = useModelStore((state) => state.setVariant)
  const clearStoreVariant = useModelStore((state) => state.clearVariant)

  const sessionModelSyncKey = sessionID ? `${directory ?? ''}:${sessionID}` : undefined

  useEffect(() => {
    if (!sessionModelSyncKey) return
    if (syncedSessionModelRef.current === sessionModelSyncKey) return
    if (!sessionAgent.model) return

    const restored = setActiveModel(sessionAgent.model)
    if (!restored) return

    syncedSessionModelRef.current = sessionModelSyncKey

    if (sessionAgent.variant) {
      setStoreVariant(sessionAgent.model, sessionAgent.variant)
    } else {
      clearStoreVariant(sessionAgent.model)
    }
  }, [clearStoreVariant, sessionAgent.model, sessionAgent.variant, sessionModelSyncKey, setActiveModel, setStoreVariant])

  const currentModel = modelString || ''
  const displayModelName = useMemo(() => {
    if (!model) {
      return currentModel
    }

    const provider = providersData?.providers.find((item) => item.id === model.providerID)
    const modelData = provider?.models?.[model.modelID]

    return modelData ? formatModelName(modelData) : model.modelID || currentModel
  }, [currentModel, model, providersData])
  const isMobile = useMobile()
  const { setShowDialog, hasForSession: hasPermissionsForSession } = usePermissions()
  const hasPendingPermissionForSession = hasPermissionsForSession(sessionID)
  const { hasVariants, currentVariant, cycleVariant } = useVariants(opcodeUrl, directory)
  const showStopButton = isSessionActive
  const hideSecondaryButtons = isMobile && isSessionActive
  const voiceFeedbackState: VoiceStatusOverlayState | null = isTogglingRecording
    ? 'starting'
    : isProcessing
      ? isVoiceAutoSendPending
        ? 'sending'
        : 'processing'
      : isRecording
        ? isVoiceSwipeArmed
          ? 'readyToSend'
          : 'recording'
        : isVoiceAutoSendPending
          ? 'sending'
          : null
  const showVoiceFeedback = voiceFeedbackState !== null
  const voiceFeedbackLabel = voiceFeedbackState === 'starting'
    ? 'Starting microphone...'
    : voiceFeedbackState === 'sending'
      ? 'Transcribing and sending...'
      : voiceFeedbackState === 'processing'
        ? 'Transcribing...'
        : voiceFeedbackState === 'readyToSend'
          ? 'Release to send'
          : voiceFeedbackState === 'recording'
            ? 'Recording... swipe up to send'
            : null
  const voiceButtonTitle = voiceFeedbackState === 'starting'
    ? 'Starting microphone'
    : voiceFeedbackState === 'sending'
      ? 'Transcribing and sending speech'
      : voiceFeedbackState === 'processing'
        ? 'Transcribing speech'
        : voiceFeedbackState === 'readyToSend'
          ? 'Release to send'
          : voiceFeedbackState === 'recording'
            ? 'Tap to transcribe'
            : 'Tap to speak'



  const renderVoiceButton = (variant: VoiceButtonVariant) => {
    const isDesktop = variant === 'desktop'
    const isBusy = isRecording || isTogglingRecording || (isProcessing && !isRecording)
    const spinnerClassName = `w-5 h-5 animate-spin rounded-full border-2 ${isDesktop ? 'border-muted-foreground' : 'border-white'} border-t-transparent`
    const voiceGestureHandlers = isDesktop ? {} : {
      onPointerDown: handleVoicePointerDown,
      onPointerMove: handleVoicePointerMove,
      onPointerUp: handleVoicePointerEnd,
      onPointerCancel: (event: ReactPointerEvent<HTMLDivElement>) => handleVoicePointerEnd(event, true),
    }
    const buttonClassName = isDesktop
      ? `hidden md:flex p-2 rounded-lg transition-all duration-200 active:scale-95 hover:scale-105 shadow-md border items-center justify-center touch-none select-none ${
        isBusy
          ? 'bg-gradient-to-br from-red-600 to-red-700 hover:from-red-500 hover:to-red-600 text-destructive-foreground border-red-500/60 animate-pulse'
          : 'bg-muted hover:bg-muted-foreground/20 text-muted-foreground hover:text-foreground border-border'
      }`
      : `px-4 py-2 rounded-lg transition-all duration-150 flex items-center justify-center min-w-[52px] border touch-none select-none ${
        isBusy
          ? 'bg-gradient-to-t from-green-700 via-green-500 to-emerald-400 text-white border-green-300/70 shadow-lg shadow-green-500/40'
          : 'bg-muted hover:bg-muted-foreground/20 text-muted-foreground hover:text-foreground border-border active:bg-muted-foreground/30 active:scale-95'
      }`

    const containerClassName = isDesktop ? 'relative hidden md:block' : 'relative flex w-full'

    return (
      <div
        ref={voiceButtonContainerRef}
        className={containerClassName}
        {...voiceGestureHandlers}
      >
        <VoiceStatusOverlay show={!isDesktop && showVoiceFeedback} label={voiceFeedbackLabel} state={voiceFeedbackState} />
        <button
          type="button"
          onClick={handleVoiceClick}
          disabled={disabled || isProcessing}
          className={buttonClassName}
          title={voiceButtonTitle}
        >
          {isTogglingRecording && !isRecording ? (
            <div className={spinnerClassName} />
          ) : isProcessing && !isRecording ? (
            <div className={spinnerClassName} />
          ) : isRecording ? (
            <MicOff className="w-5 h-5" />
          ) : (
            <Mic className="w-5 h-5" />
          )}
        </button>
      </div>
    )
  }

  

  

  

  useEffect(() => {
    onPromptChange?.(prompt.trim().length > 0)
  }, [prompt, onPromptChange])

  useEffect(() => {
    if (isRecording) {
      abortRecording()
    }
    clearSTT()
    resetVoiceGestureState()
    lastAddedTranscriptRef.current = ''
    setIsTogglingRecording(false)
    setLocalMode(null)
    // eslint-disable-next-line react-hooks/exhaustive-deps -- Intentionally only run on sessionID change to avoid clearing transcript when recording state changes
  }, [sessionID])

  

  

return (
    <div className={`relative backdrop-blur-md bg-background opacity-95 border border-border dark:border-white/30 rounded-xl p-2 md:p-3 mb-4 md:mb-1 w-full transition-all ${hasPendingPermissionForSession ? 'border-orange-500/50 ring-1 ring-orange-500/30' : ''}`}>
      {showStopButton && !(isFocused && prompt.trim().length > 0) && (
        <button
          onClick={handleStop}
          disabled={disabled}
          className="border  fixed bottom-19 right-0 md:hidden z-50 p-3 rounded-xl transition-all duration-200 active:scale-95 hover:scale-105 bg-gradient-to-br from-red-600 to-red-700 hover:from-red-500 hover:to-red-600 text-destructive-foreground border border-red-500/60 shadow-lg shadow-red-500/30"
          title="Stop"
        >
          <SquareFill className="w-5 h-5" />
        </button>
      )}

      <textarea
        ref={textareaRef}
        value={prompt}
        onChange={handleInput}
        onKeyDown={handleKeyDown}
        onPaste={handlePaste}
        onDragOver={handleDragOver}
        onDragLeave={handleDragLeave}
        onDrop={handleDrop}
        placeholder={
          isBashMode
            ? "Enter bash command..."
            : "Send a message..."
        }
        disabled={disabled}
        onFocus={() => setIsFocused(true)}
        onBlur={() => setIsFocused(false)}
        className={`w-full bg-muted/50 pl-2 md:pl-3 pr-3 py-2 text-[16px] text-foreground placeholder-muted-foreground focus:outline-none focus:bg-muted/70 resize-none min-h-[40px] max-h-[120px] disabled:opacity-50 disabled:cursor-not-allowed md:text-sm rounded-lg [field-sizing:content] ${
          isBashMode
            ? 'border-purple-500/50 bg-purple-500/5 focus:bg-purple-500/10'
            : isDragging ? 'border-blue-500/50 border-dashed bg-blue-500/5' : ''
        }`}
        rows={1}
      />

      {imageAttachments.length > 0 && (
        <div className="flex flex-wrap gap-2 px-2 py-2 mb-2">
          {imageAttachments.map((attachment) => (
            <div
              key={attachment.id}
              className="flex items-center gap-1 px-2 py-1 rounded-md bg-muted/80 border border-border text-xs text-muted-foreground"
            >
              <span className="max-w-[120px] truncate">{attachment.filename}</span>
              <button
                type="button"
                onClick={() => removeImageAttachment(attachment.id)}
                className="p-0.5 rounded hover:bg-muted-foreground/20 text-muted-foreground hover:text-foreground"
              >
                <X className="w-3 h-3" />
              </button>
            </div>
          ))}
        </div>
      )}

      <div className="flex gap-1.5 md:gap-2 items-center justify-between">
        <div className="flex gap-1.5 md:gap-2 items-center min-w-0">
          <AgentQuickSelect
            opcodeUrl={opcodeUrl}
            directory={directory}
            currentAgent={currentMode}
            onAgentChange={handleAgentChange}
            isBashMode={isBashMode}
            disabled={disabled}
          />
          {isSessionActive ? (
              <div className="px-2.5 py-1.5 md:px-3 md:py-2 rounded-lg text-xs md:text-sm font-medium text-muted-foreground max-w-[120px] md:max-w-[180px]">
                <SessionStatusIndicator sessionID={sessionID} showLabel />
              </div>
            ) : (
               !hideSecondaryButtons && (
                 <ModelQuickSelect
                   opcodeUrl={opcodeUrl}
                   directory={directory}
                   onOpenFullDialog={() => onShowModelsDialog?.()}
                 >
<button
                      className="px-2.5 py-0.5 md:px-3 min-h-[36px] min-w-0 rounded-lg text-xs md:text-sm font-medium border bg-muted border-border text-muted-foreground hover:bg-muted-foreground/10 hover:border-foreground/30 transition-colors cursor-pointer w-full md:w-auto md:max-w-[220px] dark:border-white/30 flex flex-col items-start justify-center overflow-hidden"
                    >
                      <span className="truncate w-full text-left">{displayModelName || 'Select model'}</span>
{hasVariants && currentVariant && (
                        <span className="text-[10px] text-orange-500 truncate w-full text-center capitalize">{currentVariant}</span>
                      )}
                   </button>
                 </ModelQuickSelect>
                )
             )}
          
        </div>
<div className="flex items-center gap-1.5 md:gap-2 flex-shrink-0">
            {isMobile && showScrollButton && !showVoiceFeedback ? (
              <button
                onClick={onScrollToBottom}
                className="px-4 py-2 rounded-lg bg-black hover:bg-zinc-900 text-white transition-all duration-200 active:scale-95 shadow-md flex items-center justify-center min-w-[52px] border border-zinc-700"
                title="Scroll to bottom"
              >
                <ArrowDown className="w-5 h-5" />
              </button>
            ) : !isMobile ? (
              <button
                onClick={onScrollToBottom}
                className={`p-2 rounded-lg bg-black hover:bg-zinc-900 text-white transition-all duration-200 active:scale-95 hover:scale-105 shadow-md border border-zinc-700 ${showScrollButton ? 'visible' : 'invisible'}`}
                title="Scroll to bottom"
              >
                <ArrowDown className="w-6 h-6" />
              </button>
            ) : null}
{showStopButton && (
            <button
              onClick={handleStop}
              disabled={disabled}
              className="hidden md:block p-1.5 px-5 md:p-2 md:px-6 rounded-lg transition-all duration-200 active:scale-95 hover:scale-105 bg-gradient-to-br from-red-600 to-red-700 hover:from-red-500 hover:to-red-600 text-destructive-foreground border border-red-500/60 hover:border-red-400 shadow-md shadow-red-500/30 hover:shadow-red-500/40 ring-1 ring-red-500/20 hover:ring-red-500/30"
              title="Stop"
            >
              <SquareFill className="w-4 h-4 md:w-5 md:h-5" />
            </button>
)}
          <input
            ref={fileInputRef}
            type="file"
            accept="*/*"
            className="hidden"
            onChange={handleFileInputChange}
          />
          <button
            type="button"
            onClick={() => fileInputRef.current?.click()}
            className="hidden md:block p-2 rounded-lg bg-muted hover:bg-muted-foreground/20 text-muted-foreground hover:text-foreground transition-all duration-200 active:scale-95 hover:scale-105 shadow-md border border-border"
            title="Upload image or PDF"
          >
            <Upload className="w-5 h-5" />
          </button>
          {sttEnabled && sttSupported && (
            renderVoiceButton('desktop')
          )}
          {isMobile && sttEnabled && sttSupported && !hasPendingPermissionForSession && (!showScrollButton || showVoiceFeedback) && (
            renderVoiceButton('mobile')
          )}
            <button
              data-submit-prompt
              onClick={hasPendingPermissionForSession ? () => setShowDialog(true) : handleSubmit}
              disabled={hasPendingPermissionForSession ? false : ((!prompt.trim() && imageAttachments.length === 0) || disabled)}
              className={`px-4 md:px-5 py-1.5 md:py-2 rounded-lg text-sm font-medium transition-colors dark:border flex-shrink-0 min-w-[52px] ${
                hasPendingPermissionForSession
                  ? 'bg-orange-500 hover:bg-orange-600 border-orange-400 text-primary-foreground ring-orange-500/20'
                  : 'bg-primary hover:bg-primary/90 disabled:bg-muted disabled:text-muted-foreground disabled:cursor-not-allowed text-primary-foreground border-white/30'
              }`}
              title={hasPendingPermissionForSession ? 'View pending permission' : (isStreamingResponse ? 'Queue message' : 'Send')}
            >
              <span className="whitespace-nowrap">{hasPendingPermissionForSession ? 'View' : (isStreamingResponse ? 'Queue' : 'Send')}</span>
            </button>
        </div>
      </div>
      
      <CommandSuggestions
        isOpen={showSuggestions}
        query={suggestionQuery}
        commands={filterCommands(suggestionQuery)}
        onSelect={handleCommandSelect}
        onClose={() => {
          setShowSuggestions(false)
          setSuggestionQuery('')
        }}
        selectedIndex={selectedCommandIndex}
      />
      
      <MentionSuggestions
        isOpen={showMentionSuggestions}
        items={mentionItems}
        onSelect={handleMentionSelect}
        onClose={() => {
          setShowMentionSuggestions(false)
          setMentionQuery('')
          setMentionRange(null)
        }}
        selectedIndex={selectedMentionIndex}
      />
    </div>
  )
}))
