import { useEffect, useRef, type CSSProperties } from 'react'
import { Terminal } from '@xterm/xterm'
import { FitAddon } from '@xterm/addon-fit'
import { Unicode11Addon } from '@xterm/addon-unicode11'
import { SerializeAddon } from '@xterm/addon-serialize'
import { clipboardGetText, clipboardImageToTemp, clipboardSetText, onPtyData, ptyResize, ptySpawn, ptyWrite } from '../ipc'
import type { TerminalTab } from '../types'

// tabId -> xterm 实例注册表，供指令注入后聚焦等跨组件操作
const registry = new Map<string, Terminal>()
// tabId -> 序列化插件，供移动端 attach 时回放完整回看历史
const serializers = new Map<string, SerializeAddon>()

export function focusTerminal(tabId: string) {
  registry.get(tabId)?.focus()
}

/**
 * 序列化某会话终端的完整缓冲（含回看历史，保留 ANSI 颜色），供移动端 attach 时回放全量历史。
 * 终端未挂载（未上过屏，如后台未显示的会话）时返回空串，由后端退回原始环形快照。
 */
export function serializeTerminal(tabId: string): string {
  const term = registry.get(tabId)
  if (!term) return ''
  try {
    // 直接遍历 xterm 缓冲（含回看历史）：buf.length = 回看+可见的总行数。
    // SerializeAddon 的 scrollback 选项在本环境下只回了很少的行，这里绕开它读全量。
    const buf = term.buffer.active
    const start = Math.max(0, buf.length - 8000) // 上限 8000 行，兜住体积
    let out = ''
    for (let i = start; i < buf.length; i++) {
      const line = buf.getLine(i)
      if (!line) continue
      // isWrapped=true 表示本行是上一行的换行续行，不在其前加换行，让终端按新宽度重排
      if (i > start && !line.isWrapped) out += '\r\n'
      out += line.translateToString(true) // true: 去右侧空白
    }
    return out.replace(/(\r\n)+$/, '') // 去掉尾部连续空行
  } catch {
    return ''
  }
}

/**
 * 读取某会话终端当前画面底部 maxLines 行（已去 ANSI 的纯文本）。
 * 供微信端"看屏/自动带屏"展示 TUI 菜单/选项；终端未挂载（未上过屏）时返回空串。
 */
export function readTerminalScreen(tabId: string, maxLines = 20): string {
  const term = registry.get(tabId)
  if (!term) return ''
  const buf = term.buffer.active
  const rows = term.rows
  const take = Math.min(maxLines, rows)
  const startRow = rows - take // 取可见屏幕底部 take 行（菜单/提示通常在底部）
  const out: string[] = []
  for (let i = 0; i < take; i++) {
    const line = buf.getLine(buf.baseY + startRow + i)
    out.push(line ? line.translateToString(true) : '')
  }
  while (out.length && out[0].trim() === '') out.shift()
  while (out.length && out[out.length - 1].trim() === '') out.pop()
  return out.join('\n')
}

/**
 * 把内容写入终端输入框。多行内容用括号粘贴模式包裹（不会被当作逐行回车）；
 * 单行内容（如斜杠命令）写明文，确保 TUI 将其识别为命令而非粘贴文本。
 *
 * send=true 时正文与回车分两次写、中间留一个极短间隔：让括号粘贴结束符 \x1b[201~
 * 的 flush 与 Windows ConPTY 的分片递送先落定，回车才作用在"正文已就位"的输入框上，
 * 降低回车被 TUI 在粘贴尚未完成的渲染间隙里吞掉、导致正文滞留输入框的概率。
 * 签名保持返回 void：内部用自执行 async，所有调用点（fire-and-forget）无需改动。
 */
export function writeToTerminal(tabId: string, content: string, send: boolean) {
  const payload = content.includes('\n') ? `\x1b[200~${content}\x1b[201~` : content
  void (async () => {
    await ptyWrite(tabId, payload)
    if (send) {
      await new Promise((r) => setTimeout(r, 60))
      await ptyWrite(tabId, '\r')
    }
    focusTerminal(tabId)
  })()
}

/**
 * 粘贴处理：由 Rust 直接读系统剪贴板（不依赖 WebView 的粘贴事件，
 * 它对截图类剪贴板图片的暴露不可靠）。文本走括号粘贴；
 * 只有图片时编码 PNG 落盘临时目录并插入带引号的文件路径。
 */
async function pasteFromClipboard(tabId: string) {
  const text = await clipboardGetText()
  if (text) {
    void ptyWrite(tabId, `\x1b[200~${text}\x1b[201~`)
    return
  }
  const path = await clipboardImageToTemp()
  if (path) {
    void ptyWrite(tabId, `\x1b[200~"${path}"\x1b[201~`)
  }
}

function themeFor(dark: boolean) {
  return dark
    ? {
        background: '#1d1f21',
        foreground: '#d6d9dc',
        cursor: '#d6d9dc',
        selectionBackground: '#3a4a58',
        black: '#1d1f21',
        red: '#c4756d',
        green: '#84ad88',
        yellow: '#c9a05e',
        blue: '#6fa3c8',
        magenta: '#a98bb8',
        cyan: '#7fb2af',
        white: '#d6d9dc',
        brightBlack: '#62686e',
        brightRed: '#d99089',
        brightGreen: '#9cc4a0',
        brightYellow: '#dbb678',
        brightBlue: '#8db8d8',
        brightMagenta: '#bda4ca',
        brightCyan: '#99c6c3',
        brightWhite: '#eceff1'
      }
    : {
        background: '#fafaf9',
        foreground: '#1f2328',
        cursor: '#1f2328',
        selectionBackground: '#c7d8e4',
        black: '#1f2328',
        red: '#a4453c',
        green: '#4a7d4f',
        yellow: '#a8762f',
        blue: '#3d6f94',
        magenta: '#7d5a8c',
        cyan: '#3e7e7a',
        white: '#e8e8e6',
        brightBlack: '#6b7178',
        brightRed: '#c05a50',
        brightGreen: '#5e9663',
        brightYellow: '#c08c42',
        brightBlue: '#5589ae',
        brightMagenta: '#96719f',
        brightCyan: '#549893',
        brightWhite: '#fafaf9'
      }
}

interface Props {
  tab: TerminalTab
  /** 栏位几何样式（百分比定位）；null 表示保活但不可见 */
  paneStyle: CSSProperties | null
  /** 是否为聚焦栏位（指令面板/路径填入的目标） */
  focused: boolean
  onFocus: () => void
  dark: boolean
}

export function TerminalView({ tab, paneStyle, focused, onFocus, dark }: Props) {
  const visible = paneStyle !== null
  const hostRef = useRef<HTMLDivElement>(null)
  const termRef = useRef<Terminal | null>(null)
  const fitRef = useRef<FitAddon | null>(null)
  const visibleRef = useRef(visible)
  visibleRef.current = visible
  const onFocusRef = useRef(onFocus)
  onFocusRef.current = onFocus

  // 创建 xterm + 启动 PTY，仅在挂载时执行一次
  useEffect(() => {
    const host = hostRef.current
    if (!host) return

    const term = new Terminal({
      allowProposedApi: true,
      fontFamily: 'Consolas, "Cascadia Mono", "Courier New", monospace',
      fontSize: 13,
      lineHeight: 1.15,
      cursorBlink: true,
      scrollback: 10000,
      theme: themeFor(dark)
    })
    const fit = new FitAddon()
    term.loadAddon(fit)
    term.loadAddon(new Unicode11Addon())
    term.unicode.activeVersion = '11'
    const serializer = new SerializeAddon()
    term.loadAddon(serializer)
    term.open(host)
    fit.fit()
    termRef.current = term
    fitRef.current = fit
    registry.set(tab.id, term)
    serializers.set(tab.id, serializer)

    // OSC 52 剪贴板写入:TUI 程序(如 Claude Code 选区复制时提示 "copied N chars")
    // 通过 ESC]52;Pc;<base64> 请求终端把内容写进系统剪贴板。xterm 默认不实现,
    // 这里自行解析:handler 收到的 data 形如 "Pc;Pd",分号后才是 base64 负载
    // (Pd 为 "?" 表示请求回读剪贴板,出于隐私不支持)。base64 按 UTF-8 解码避免
    // 中文/路径乱码,最终落盘走 Rust 端 arboard。
    term.parser.registerOscHandler(52, (data) => {
      const sep = data.indexOf(';')
      if (sep < 0) return true // 畸形序列:无 Pc;Pd 分隔,忽略但视为已处理
      const payload = data.slice(sep + 1)
      if (payload === '?') return true
      try {
        const bytes = Uint8Array.from(atob(payload), (c) => c.charCodeAt(0))
        const text = new TextDecoder().decode(bytes)
        if (text) void clipboardSetText(text)
      } catch {
        // base64 非法时静默忽略
      }
      return true
    })

    term.onData((data) => void ptyWrite(tab.id, data))
    term.onResize(({ cols, rows }) => void ptyResize(tab.id, cols, rows))

    // 完全接管粘贴：Ctrl+V 在 keydown 层拦截，paste 事件兜底（右键菜单粘贴、
    // Shift+Insert 等触发方式）；时间戳护栏避免两条路径重复执行
    let lastPaste = 0
    const doPaste = () => {
      const now = Date.now()
      if (now - lastPaste < 150) return
      lastPaste = now
      void pasteFromClipboard(tab.id)
    }
    term.attachCustomKeyEventHandler((ev) => {
      if (ev.type !== 'keydown') return true
      const ctrlOnly = ev.ctrlKey && !ev.shiftKey && !ev.altKey
      // Ctrl+V：完全接管粘贴
      if (ctrlOnly && (ev.key === 'v' || ev.key === 'V')) {
        ev.preventDefault()
        doPaste()
        return false
      }
      // Ctrl+C：有选区时复制选区并清除选区（与 Windows 终端一致，复制后再按一次即为中断）。
      // 复制走 Rust 端剪贴板（arboard），避免使用者 WebView2 环境下 navigator.clipboard 不可用。
      // 无选区时 return true 放行，让 xterm 正常发送中断信号 \x03（保留 CLI 的 Ctrl+C 中断习惯）。
      if (ctrlOnly && (ev.key === 'c' || ev.key === 'C') && term.hasSelection()) {
        ev.preventDefault()
        void clipboardSetText(term.getSelection())
        term.clearSelection()
        return false
      }
      return true
    })
    const onPasteCapture = (e: ClipboardEvent) => {
      e.preventDefault()
      e.stopPropagation()
      doPaste()
    }
    host.addEventListener('paste', onPasteCapture, true)

    // 右键:有选区即复制并清除,无选区则粘贴(Windows Terminal 风格)
    const onContextMenu = (e: MouseEvent) => {
      e.preventDefault()
      if (term.hasSelection()) {
        void clipboardSetText(term.getSelection())
        term.clearSelection()
      } else {
        doPaste()
      }
    }
    host.addEventListener('contextmenu', onContextMenu)

    // 点击栏位即聚焦该栏（指令面板等操作的目标随之切换）
    const onMouseDown = () => onFocusRef.current()
    host.addEventListener('mousedown', onMouseDown)

    // 订阅 PTY 输出（按 tabId 过滤），base64 -> 字节流交给 xterm 解码
    let unlisten: (() => void) | null = null
    let cancelled = false
    void (async () => {
      const fn = await onPtyData((p) => {
        if (p.tabId !== tab.id) return
        const bin = atob(p.data)
        const bytes = new Uint8Array(bin.length)
        for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i)
        term.write(bytes)
      })
      if (cancelled) {
        fn()
        return
      }
      unlisten = fn
      // 监听就绪后再启动 claude，避免最早的输出丢失
      try {
        await ptySpawn(tab.id, tab.projectPath, tab.args, term.cols, term.rows)
      } catch (e) {
        term.write(`\r\n\x1b[31m启动 claude 失败: ${String(e)}\x1b[0m\r\n`)
      }
    })()

    const observer = new ResizeObserver(() => {
      if (visibleRef.current) fit.fit()
    })
    observer.observe(host)

    return () => {
      cancelled = true
      host.removeEventListener('paste', onPasteCapture, true)
      host.removeEventListener('contextmenu', onContextMenu)
      host.removeEventListener('mousedown', onMouseDown)
      observer.disconnect()
      unlisten?.()
      registry.delete(tab.id)
      serializers.delete(tab.id)
      term.dispose()
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [])

  // 切回可见时重新适配尺寸（几何变化由 ResizeObserver 接管）
  useEffect(() => {
    if (visible && fitRef.current) {
      requestAnimationFrame(() => {
        fitRef.current?.fit()
      })
    }
  }, [visible])

  // 聚焦栏位时把键盘焦点交给该终端
  useEffect(() => {
    if (visible && focused) {
      requestAnimationFrame(() => termRef.current?.focus())
    }
  }, [visible, focused])

  // 主题切换
  useEffect(() => {
    if (termRef.current) {
      termRef.current.options.theme = themeFor(dark)
    }
  }, [dark])

  return (
    <div
      ref={hostRef}
      className={`terminal-host ${visible ? '' : 'hidden'} ${visible && focused ? 'focused' : ''}`}
      style={visible ? paneStyle : undefined}
    />
  )
}
