import * as React from 'react'

import { cn } from './utils'

// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------

type LineType = 'command' | 'output' | 'error' | 'greeting'

interface OutputLine {
  text: string
  type: LineType
}

export interface TerminalProps extends React.ComponentProps<'div'> {
  prompt?: string
  greeting?: string
  onCommand?: (command: string) => string | Promise<string>
  maxHistory?: number
  maxLines?: number
  fontSize?: number
  fontFamily?: string
}

// ---------------------------------------------------------------------------
// Line type styling
// ---------------------------------------------------------------------------

const lineStyles: Record<LineType, string> = {
  command: 'text-foreground',
  output: 'text-foreground-secondary',
  error: 'text-accent-rose',
  greeting: 'text-accent-cyan',
}

// ---------------------------------------------------------------------------
// Terminal
// ---------------------------------------------------------------------------

function Terminal({
  prompt = '$ ',
  greeting,
  onCommand,
  maxHistory = 100,
  maxLines = 1000,
  fontSize = 13,
  fontFamily = 'Menlo, Monaco, Consolas, monospace',
  className,
  ...props
}: TerminalProps) {
  const [lines, setLines] = React.useState<OutputLine[]>(() =>
    greeting ? [{ text: greeting, type: 'greeting' as const }] : []
  )
  const [inputValue, setInputValue] = React.useState('')
  const [processing, setProcessing] = React.useState(false)

  const historyRef = React.useRef<string[]>([])
  const historyIndexRef = React.useRef(-1)
  const currentInputRef = React.useRef('')
  const outputRef = React.useRef<HTMLDivElement>(null)
  const inputRef = React.useRef<HTMLInputElement>(null)

  const addLines = React.useCallback(
    (text: string, type: LineType) => {
      const parts = text.split('\n')
      setLines(prev => {
        const next = [...prev, ...parts.map(t => ({ text: t, type }))]
        return next.length > maxLines ? next.slice(next.length - maxLines) : next
      })
    },
    [maxLines]
  )

  // Auto-scroll when lines change
  React.useEffect(() => {
    if (outputRef.current) {
      outputRef.current.scrollTop = outputRef.current.scrollHeight
    }
  }, [lines])

  const focusInput = React.useCallback(() => {
    inputRef.current?.focus()
  }, [])

  const submitCommand = React.useCallback(async () => {
    const command = inputValue.trim()
    setInputValue('')
    historyIndexRef.current = -1
    currentInputRef.current = ''

    addLines(`${prompt}${command}`, 'command')

    if (command.length === 0) return

    // Push to history
    const history = historyRef.current
    if (history[history.length - 1] !== command) {
      history.push(command)
      if (history.length > maxHistory) history.shift()
    }

    // Built-in clear
    if (command === 'clear') {
      setLines([])
      return
    }

    if (onCommand) {
      setProcessing(true)
      try {
        const result = await onCommand(command)
        if (result.length > 0) addLines(result, 'output')
      } catch (err) {
        const message = err instanceof Error ? err.message : String(err)
        addLines(message, 'error')
      } finally {
        setProcessing(false)
        focusInput()
      }
    }
  }, [inputValue, prompt, onCommand, maxHistory, addLines, focusInput])

  const navigateHistory = React.useCallback(
    (direction: number) => {
      const history = historyRef.current
      if (history.length === 0) return

      if (historyIndexRef.current === -1 && direction === -1) {
        currentInputRef.current = inputValue
        historyIndexRef.current = history.length
      }

      const newIndex = historyIndexRef.current + direction

      if (newIndex < 0) return

      if (newIndex >= history.length) {
        historyIndexRef.current = -1
        setInputValue(currentInputRef.current)
        return
      }

      historyIndexRef.current = newIndex
      setInputValue(history[newIndex] ?? '')
    },
    [inputValue]
  )

  const handleKeyDown = React.useCallback(
    (e: React.KeyboardEvent) => {
      switch (e.key) {
        case 'Enter':
          e.preventDefault()
          if (!processing) void submitCommand()
          break
        case 'ArrowUp':
          e.preventDefault()
          navigateHistory(-1)
          break
        case 'ArrowDown':
          e.preventDefault()
          navigateHistory(1)
          break
        case 'c':
          if (e.ctrlKey) {
            e.preventDefault()
            addLines(`${prompt}${inputValue}^C`, 'command')
            setInputValue('')
            historyIndexRef.current = -1
          }
          break
        case 'l':
          if (e.ctrlKey) {
            e.preventDefault()
            setLines([])
          }
          break
      }
    },
    [processing, submitCommand, navigateHistory, addLines, prompt, inputValue]
  )

  return (
    <div
      data-slot="terminal"
      className={cn(
        'bg-glass border-border-glass flex flex-col rounded-lg border backdrop-blur-[20px]',
        'overflow-hidden',
        className
      )}
      style={{ fontFamily, fontSize: `${fontSize}px` }}
      onClick={focusInput}
      {...props}
    >
      {/* Output */}
      <div ref={outputRef} className="flex-1 overflow-auto p-3" role="log" aria-live="polite">
        {lines.map((line, i) => (
          <div key={i} className={cn('whitespace-pre-wrap', lineStyles[line.type])}>
            {line.text}
          </div>
        ))}
      </div>

      {/* Input line */}
      <div className="border-border-glass flex items-center border-t px-3 py-2">
        <span className="text-accent-cyan shrink-0">{prompt}</span>
        <input
          ref={inputRef}
          type="text"
          className="text-foreground min-w-0 flex-1 bg-transparent outline-none"
          value={inputValue}
          onChange={e => setInputValue(e.target.value)}
          onKeyDown={handleKeyDown}
          disabled={processing}
          autoComplete="off"
          autoCorrect="off"
          autoCapitalize="off"
          spellCheck={false}
          aria-label="Terminal input"
        />
      </div>
    </div>
  )
}
Terminal.displayName = 'Terminal'

export { Terminal }
