import { useEffect, useRef, useState } from 'react'
import { Terminal as XTerminal } from '@xterm/xterm'
import { FitAddon } from '@xterm/addon-fit'
import '@xterm/xterm/css/xterm.css'
import { Copy, Info, RefreshCw } from 'lucide-react'
import { useTerminalCmds } from '@/api/hooks'
import { Button } from '@/components/ui'
import { useTheme, type ThemeId } from '@/context/ThemeContext'
import { getTerminalTheme } from '@/lib/terminal-theme'
import { toast } from '@/lib/toast'

type StatusState = { text: string; type: 'success' | 'error' | 'info' } | null

/** xterm 要求 theme 必须是新对象引用才会生效 */
function applyXtermTheme(term: XTerminal, themeId: ThemeId, host?: HTMLElement | null) {
  const next = { ...getTerminalTheme(themeId) }
  // 文档明确：对象选项需新引用，且可用 options 批量赋值
  term.options = { theme: next }
  if (host) {
    host.style.backgroundColor = String(next.background || '')
    host.style.color = String(next.foreground || '')
    host.dataset.termTheme = themeId
  }
  try {
    // 强制重绘全部可见行
    const rows = Math.max(1, term.rows)
    term.refresh(0, rows - 1)
  } catch {
    // ignore
  }
}

export default function TerminalPage() {
  const { theme } = useTheme()
  const themeRef = useRef<ThemeId>(theme)
  themeRef.current = theme

  const containerRef = useRef<HTMLDivElement>(null)
  const termRef = useRef<XTerminal | null>(null)
  const fitRef = useRef<FitAddon | null>(null)
  const wsRef = useRef<WebSocket | null>(null)
  const isPtyModeRef = useRef(false)
  const inputBufferRef = useRef('')
  const historyRef = useRef<string[]>([])
  const historyIndexRef = useRef(-1)
  const [status, setStatus] = useState<StatusState>({ text: '正在初始化终端...', type: 'info' })
  const [reconnecting, setReconnecting] = useState(false)
  const [copied, setCopied] = useState('')
  const [showCmds, setShowCmds] = useState(false)
  const { data: cmdsData } = useTerminalCmds()
  const cmds = Array.isArray(cmdsData) ? cmdsData : []

  function safeFit() {
    try {
      const term = termRef.current
      const fit = fitRef.current
      const el = containerRef.current
      if (!term || !fit || !el) return
      if (el.clientWidth < 10 || el.clientHeight < 10) return
      fit.fit()
      if (isPtyModeRef.current && wsRef.current?.readyState === WebSocket.OPEN) {
        wsRef.current.send(JSON.stringify({ type: 'resize', cols: term.cols, rows: term.rows }))
      }
    } catch {
      // ignore
    }
  }

  function disposeSocket() {
    const ws = wsRef.current
    if (ws) {
      try {
        if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) ws.close()
      } catch {
        // ignore
      }
    }
    wsRef.current = null
  }

  function disposeTerminal() {
    disposeSocket()
    try {
      termRef.current?.dispose()
    } catch {
      // ignore
    }
    termRef.current = null
    fitRef.current = null
    isPtyModeRef.current = false
    inputBufferRef.current = ''
  }

  function connectWebSocket() {
    disposeSocket()
    const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
    const baseUrl = (window as any).__BASE_URL__ || ''
    const apiVersion = (window as any).__API_VERSION__ || '/api/v1'
    const wsUrl = `${protocol}//${window.location.host}${baseUrl}${apiVersion}/terminal/ws`

    let ws: WebSocket
    try {
      ws = new WebSocket(wsUrl)
    } catch {
      setStatus({ text: '无法创建 WebSocket 连接', type: 'error' })
      return
    }
    wsRef.current = ws

    ws.onopen = () => {
      setStatus({ text: '已连接到终端', type: 'success' })
      // 连接后再次套用当前主题，防止初始化时序问题
      if (termRef.current) applyXtermTheme(termRef.current, themeRef.current, containerRef.current)
      safeFit()
      termRef.current?.focus()
    }

    ws.onmessage = (event) => {
      const data = event.data
      if (data === '__PTY_MODE__') {
        isPtyModeRef.current = true
        safeFit()
        return
      }
      if (data === '__PIPE_MODE__') {
        isPtyModeRef.current = false
        safeFit()
        return
      }
      termRef.current?.write(String(data))
    }

    ws.onclose = () => setStatus({ text: '连接已断开', type: 'error' })
    ws.onerror = () => setStatus({ text: '连接错误', type: 'error' })
  }

  function initTerminal() {
    if (!containerRef.current) return
    disposeTerminal()
    containerRef.current.innerHTML = ''

    setStatus({ text: '正在初始化终端...', type: 'info' })

    const currentTheme = themeRef.current
    const termTheme = { ...getTerminalTheme(currentTheme) }

    const term = new XTerminal({
      cursorBlink: true,
      convertEol: true,
      fontSize: 14,
      lineHeight: 1.3,
      letterSpacing: 0.4,
      fontWeight: '400',
      fontWeightBold: '700',
      fontFamily: 'var(--font-mono), ui-monospace, SFMono-Regular, Consolas, Menlo, monospace',
      scrollback: 5000,
      drawBoldTextInBrightColors: true,
      theme: termTheme,
    })

    const fitAddon = new FitAddon()
    term.loadAddon(fitAddon)
    term.open(containerRef.current)
    applyXtermTheme(term, currentTheme, containerRef.current)

    const xtermEl = containerRef.current.querySelector('.xterm') as HTMLElement | null
    if (xtermEl) {
      xtermEl.style.height = '100%'
      xtermEl.style.width = '100%'
    }
    termRef.current = term
    fitRef.current = fitAddon

    term.attachCustomKeyEventHandler((e) => {
      if (e.ctrlKey && e.code === 'KeyC' && e.type === 'keydown') {
        const selection = term.getSelection()
        if (selection) {
          navigator.clipboard
            .writeText(selection)
            .then(() => {
              setCopied('已复制')
              toast.success('已复制选中内容')
            })
            .catch(() => {
              toast.error('复制失败')
            })
          return false
        }
      }
      return true
    })

    term.onData((data) => {
      const ws = wsRef.current
      if (!ws || ws.readyState !== WebSocket.OPEN) return

      if (isPtyModeRef.current) {
        ws.send(data)
        return
      }

      if (data === '\r') {
        term.write('\r\n')
        if (inputBufferRef.current.trim()) {
          historyRef.current.push(inputBufferRef.current)
          historyIndexRef.current = historyRef.current.length
          ws.send(inputBufferRef.current + '\r\n')
        }
        inputBufferRef.current = ''
      } else if (data === '\x1b[A') {
        if (historyRef.current.length > 0 && historyIndexRef.current > 0) {
          for (let i = 0; i < inputBufferRef.current.length; i++) term.write('\b \b')
          historyIndexRef.current--
          inputBufferRef.current = historyRef.current[historyIndexRef.current] ?? ''
          term.write(inputBufferRef.current)
        }
      } else if (data === '\x1b[B') {
        for (let i = 0; i < inputBufferRef.current.length; i++) term.write('\b \b')
        if (historyIndexRef.current < historyRef.current.length - 1) {
          historyIndexRef.current++
          inputBufferRef.current = historyRef.current[historyIndexRef.current] ?? ''
          term.write(inputBufferRef.current)
        } else {
          historyIndexRef.current = historyRef.current.length
          inputBufferRef.current = ''
        }
      } else if (data === '\x7f' || data === '\b') {
        if (inputBufferRef.current.length > 0) {
          inputBufferRef.current = inputBufferRef.current.slice(0, -1)
          term.write('\b \b')
        }
      } else if (data === '\x03') {
        ws.send('\x03')
        inputBufferRef.current = ''
        historyIndexRef.current = historyRef.current.length
        term.write('^C\r\n')
      } else if (data === '\x0c') {
        if (!inputBufferRef.current) {
          ws.send('clear\r\n')
        } else {
          term.clear()
          term.write(inputBufferRef.current)
        }
      } else if (data >= ' ' || data === '\t') {
        inputBufferRef.current += data
        term.write(data)
      }
    })

    window.setTimeout(() => {
      // 再套一次主题（确保 open 后 canvas/dom 渲染器吃到）
      applyXtermTheme(term, themeRef.current, containerRef.current)
      safeFit()
      term.focus()
      window.setTimeout(() => {
        connectWebSocket()
        try {
          const pending = sessionStorage.getItem('taskpool_terminal_cmd')
          if (pending) {
            sessionStorage.removeItem('taskpool_terminal_cmd')
            inputBufferRef.current = pending
            term.write(pending)
            setStatus({ text: '已预填命令，按回车执行', type: 'info' })
          }
        } catch {
          // ignore
        }
      }, 150)
    }, 80)
  }

  function reconnect() {
    setReconnecting(true)
    initTerminal()
    window.setTimeout(() => setReconnecting(false), 1000)
  }

  async function copyCmd(name: string) {
    const text = `taskpool ${name}`
    try {
      await navigator.clipboard.writeText(text)
      setCopied(text)
      toast.success(`已复制: ${text}`)
      window.setTimeout(() => setCopied((c) => (c === text ? '' : c)), 1500)
    } catch {
      toast.error('复制失败')
    }
  }

  // 初始化终端（只建一次实例）
  useEffect(() => {
    const timer = window.setTimeout(initTerminal, 120)
    const onResize = () => safeFit()
    window.addEventListener('resize', onResize)
    return () => {
      window.clearTimeout(timer)
      window.removeEventListener('resize', onResize)
      disposeTerminal()
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [])

  // 主题切换：等 data-theme / CSS 变量落盘后再套用 xterm 主题
  useEffect(() => {
    themeRef.current = theme
    let cancelled = false
    const run = () => {
      if (cancelled) return
      const term = termRef.current
      if (!term) return
      applyXtermTheme(term, theme, containerRef.current)
    }
    // 双 rAF：确保 ThemeProvider 已写入 data-theme，且浏览器完成样式计算
    const id1 = window.requestAnimationFrame(() => {
      const id2 = window.requestAnimationFrame(run)
      // 再补一帧延迟，覆盖部分浏览器样式批处理
      window.setTimeout(run, 0)
      void id2
    })
    // 监听 html[data-theme] 变化作为兜底（顶栏切换一定改这个）
    const root = document.documentElement
    const mo = new MutationObserver((mutations) => {
      for (const m of mutations) {
        if (m.type === 'attributes' && m.attributeName === 'data-theme') {
          run()
        }
      }
    })
    mo.observe(root, { attributes: true, attributeFilter: ['data-theme'] })
    return () => {
      cancelled = true
      window.cancelAnimationFrame(id1)
      mo.disconnect()
    }
  }, [theme])

  const statusColor =
    status?.type === 'success'
      ? 'text-[var(--success)]'
      : status?.type === 'error'
        ? 'text-[var(--danger)]'
        : 'text-[var(--info)]'
  const statusDot =
    status?.type === 'success'
      ? 'bg-[var(--success)]'
      : status?.type === 'error'
        ? 'bg-[var(--danger)]'
        : 'bg-[var(--info)]'

  return (
    <div className="flex h-[calc(100dvh-7.5rem)] min-h-[420px] gap-3 overflow-hidden">
      {/* 左侧：内置命令常驻列表 */}
      <aside className="flex w-[280px] shrink-0 flex-col overflow-hidden rounded-md border border-[var(--border)] bg-[var(--bg-secondary)]">
        <div className="flex shrink-0 items-center justify-between border-b border-[var(--border)] px-3 py-2.5">
          <div className="text-xs font-medium text-[var(--text-primary)]">内置命令</div>
          <div className="text-[11px] text-[var(--text-muted)]">{cmds.length}</div>
        </div>
        <div className="min-h-0 flex-1 overflow-y-auto">
          {cmds.length === 0 ? (
            <div className="px-3 py-8 text-center text-xs text-[var(--text-muted)]">暂无命令</div>
          ) : (
            <ul className="divide-y divide-[var(--border)]">
              {cmds.map((cmd) => {
                const full = `taskpool ${cmd.name}`
                const isCopied = copied === full
                return (
                  <li key={cmd.name}>
                    <button
                      type="button"
                      className="flex w-full items-start gap-2 px-3 py-2.5 text-left transition-colors hover:bg-[var(--bg-hover)]"
                      onClick={() => copyCmd(cmd.name)}
                      title="点击复制命令"
                    >
                      <div className="min-w-0 flex-1">
                        <code className="mb-1 inline-block max-w-full truncate rounded border border-[var(--border)] bg-[var(--bg-tertiary)] px-1.5 py-0.5 font-mono text-[11px] text-[var(--text-accent)]">
                          {full}
                        </code>
                        <p className="line-clamp-2 text-[12px] leading-5 text-[var(--text-secondary)]">
                          {cmd.description || '暂无说明'}
                        </p>
                      </div>
                      <span className="mt-0.5 inline-flex h-5 w-5 shrink-0 items-center justify-center text-[var(--text-muted)]">
                        {isCopied ? (
                          <span className="text-[10px] whitespace-nowrap text-[var(--success)]">已复制</span>
                        ) : (
                          <Copy size={13} />
                        )}
                      </span>
                    </button>
                  </li>
                )
              })}
            </ul>
          )}
        </div>
        <div className="shrink-0 border-t border-[var(--border)] px-3 py-2 text-[10px] text-[var(--text-muted)]">
          点击命令即可复制
        </div>
      </aside>

      {/* 右侧：终端 */}
      <div className="flex min-w-0 flex-1 flex-col overflow-hidden rounded-md border border-[var(--border)] bg-[var(--bg-secondary)]">
        <div className="flex shrink-0 items-center justify-between border-b border-[var(--border)] px-3 py-2">
          <span className="text-xs font-medium text-[var(--text-primary)]">终端</span>
          <div className="flex items-center gap-3">
            {status ? (
              <div className="flex items-center gap-1.5">
                <span className={`h-1.5 w-1.5 rounded-full ${statusDot}`} />
                <span className={`text-[11px] font-medium ${statusColor}`}>{status.text}</span>
              </div>
            ) : null}
            <Button variant="ghost" size="icon" onClick={reconnect} disabled={reconnecting} title="重新连接">
              <RefreshCw size={14} className={reconnecting ? 'animate-spin' : ''} />
            </Button>
          </div>
        </div>

        <div className="relative min-h-0 flex-1 overflow-hidden bg-[var(--bg-secondary)]" data-theme-sync={theme}>
          <div ref={containerRef} className="terminal-host absolute inset-0 h-full w-full px-3 py-2" />
        </div>
      </div>
    </div>
  )
}
