<template>
  <header>
    <!-- 功能 -->
    <!-- <el-button type="primary" @click="handleClear">
      清空
    </el-button>
    <el-button type="primary" @click="handlePaste">
      粘贴
    </el-button> -->
  </header>
  <div ref="terminalRefs" class="terminal-container" />
</template>

<script setup>
import { ref, onMounted, onBeforeUnmount, getCurrentInstance } from 'vue'
import { Terminal } from 'xterm'
import 'xterm/css/xterm.css'
import { FitAddon } from 'xterm-addon-fit'
import { SearchAddon } from 'xterm-addon-search'
import { SearchBarAddon } from 'xterm-addon-search-bar'
import { WebLinksAddon } from 'xterm-addon-web-links'
import socketIo from 'socket.io-client'

const { io } = socketIo
const { proxy: { $api, $serviceURI, $notification, $router, $messageBox } } = getCurrentInstance()

const props = defineProps({
  token: {
    required: true,
    type: String
  },
  host: {
    required: true,
    type: String
  },
  tabKey: {
    required: true,
    type: String
  }
})

const socket = ref(null)
const term = ref(null)
const command = ref('')
const timer = ref(null)
const fitAddon = ref(null)
const searchBar = ref(null)
const isManual = ref(false)
const terminalRefs = ref(null)

const tabKey = ref(props.tabKey)

const getCommand = async () => {
  let { data } = await $api.getCommand(props.host)
  if (data) command.value = data
}

const connectIO = () => {
  const { host, token } = props
  socket.value = io($serviceURI, {
    path: '/terminal',
    forceNew: false,
    reconnectionAttempts: 1
  })

  socket.value.on('connect', () => {
    console.log('/terminal socket已连接：', socket.value.id)
    socket.value.emit('create', { host, token })
    socket.value.on('connect_success', () => {
      onData()
      socket.value.on('connect_terminal', () => {
        onResize()
        onFindText()
        onWebLinks()
        if (command.value) socket.value.emit('input', command.value + '\n')
      })
    })
    socket.value.on('create_fail', (message) => {
      console.error(message)
      $notification({
        title: '创建失败',
        message,
        type: 'error'
      })
    })
    socket.value.on('token_verify_fail', () => {
      $notification({
        title: 'Error',
        message: 'token校验失败，请重新登录',
        type: 'error'
      })
      $router.push('/login')
    })
    socket.value.on('connect_fail', (message) => {
      console.error(message)
      $notification({
        title: '连接失败',
        message,
        type: 'error'
      })
    })
  })

  socket.value.on('disconnect', () => {
    console.warn('terminal websocket 连接断开')
    if (!isManual.value) reConnect()
  })

  socket.value.on('connect_error', (err) => {
    console.error('terminal websocket 连接错误：', err)
    $notification({
      title: '终端连接失败',
      message: '请检查socket服务是否正常',
      type: 'error'
    })
  })
}

const reConnect = () => {
  socket.value.close && socket.value.close()
  $messageBox.alert(
    '<strong>终端连接断开</strong>',
    'Error',
    {
      dangerouslyUseHTMLString: true,
      confirmButtonText: '刷新页面'
    }
  ).then(() => {
    location.reload()
  })
}

const createLocalTerminal = () => {
  let terminal = new Terminal({
    rendererType: 'dom',
    bellStyle: 'sound',
    convertEol: true,
    cursorBlink: true,
    disableStdin: false,
    fontSize: 18,
    minimumContrastRatio: 7,
    theme: {
      foreground: '#ECECEC',
      background: '#000000',
      cursor: 'help',
      selection: '#ff9900',
      lineHeight: 20
    }
  })
  term.value = terminal
  terminal.open(terminalRefs.value)
  terminal.writeln('\x1b[1;32mWelcome to EasyNode terminal\x1b[0m.')
  terminal.writeln('\x1b[1;32mAn experimental Web-SSH Terminal\x1b[0m.')
  terminal.focus()
  onSelectionChange()
}

const onResize = () => {
  fitAddon.value = new FitAddon()
  term.value.loadAddon(fitAddon.value)
  fitAddon.value.fit()
  let { rows, cols } = term.value
  socket.value.emit('resize', { rows, cols })
  window.addEventListener('resize', handleResize)
}

const handleResize = () => {
  if (timer.value) clearTimeout(timer.value)
  timer.value = setTimeout(() => {
    let temp = []
    let panes = Array.from(document.getElementsByClassName('el-tab-pane'))
    panes.forEach((item, index) => {
      temp[index] = item.style.display
      item.style.display = 'block'
    })
    fitAddon.value?.fit()
    panes.forEach((item, index) => {
      item.style.display = temp[index]
    })
    let { rows, cols } = term.value
    socket.value?.emit('resize', { rows, cols })
    focusTab()
  }, 200)
}

const onWebLinks = () => {
  term.value.loadAddon(new WebLinksAddon())
}

const onFindText = () => {
  const searchAddon = new SearchAddon()
  searchBar.value = new SearchBarAddon({ searchAddon })
  term.value.loadAddon(searchAddon)
  term.value.loadAddon(searchBar.value)
}

const onSelectionChange = () => {
  term.value.onSelectionChange(() => {
    let str = term.value.getSelection()
    if (!str) return
    const text = new Blob([str], { type: 'text/plain' })
    const item = new ClipboardItem({
      'text/plain': text
    })
    navigator.clipboard.write([item])
  })
}

const onData = () => {
  socket.value.on('output', (str) => {
    term.value.write(str)
  })
  term.value.onData((key) => {
    let acsiiCode = key.codePointAt()
    if (acsiiCode === 22) return handlePaste()
    if (acsiiCode === 6) return searchBar.value.show()
    socket.value.emit('input', key)
  })
}

const handleClear = () => {
  term.value.clear()
}

const handlePaste = async () => {
  let str = await navigator.clipboard.readText()
  socket.value.emit('input', str)
  term.value.focus()
}

const focusTab = () => {
  term.value.blur()
  setTimeout(() => {
    term.value.focus()
  }, 200)
}

const handleInputCommand = (command) => {
  socket.value.emit('input', command)
}

onMounted(async () => {
  createLocalTerminal()
  await getCommand()
  connectIO()
})

onBeforeUnmount(() => {
  isManual.value = true
  socket.value?.close()
  window.removeEventListener('resize', handleResize)
})


defineExpose({
  focusTab,
  handleResize,
  handleInputCommand,
  tabKey
})
</script>

<style lang="scss" scoped>
header {
  position: fixed;
  z-index: 1;
  right: 10px;
  top: 50px;
}

.terminal-container {
  height: 100%;

  :deep(.xterm-viewport),
  :deep(.xterm-screen) {
    width: 100% !important;
    height: 100% !important;

    // 滚动条整体部分
    &::-webkit-scrollbar {
      height: 5px;
      width: 5px;
      background-color: #ffffff;
    }

    // 底层轨道
    &::-webkit-scrollbar-track {
      background-color: #000;
      border-radius: 0;
    }

    // 滚动滑块
    &::-webkit-scrollbar-thumb {
      border-radius: 5px;
    }

    &::-webkit-scrollbar-thumb:hover {
      background-color: #067ef7;
    }
  }
}
</style>

<style lang="scss">
.terminals {
  .el-tabs__header {
    padding-left: 55px;
  }
}
</style>
