<script lang="ts" setup>
import { message } from 'ant-design-vue/es';
import { ref, reactive, onMounted, onBeforeUnmount, nextTick } from 'vue';
import { FitAddon } from '@xterm/addon-fit';
import { Terminal } from '@xterm/xterm';
import '@xterm/xterm/css/xterm.css';
import { RESULT_CODE_ERROR } from '@/constants/result-constants';
import { OptionsType, WS } from '@/plugins/ws-websocket';
const ws = new WS();
const emit = defineEmits(['connect', 'close', 'message']);
const props = defineProps({
  /**终端ID，必传 */
  id: {
    type: String,
    required: true,
  },
  /**连接主机ID，必传 */
  hostId: {
    type: Number,
    required: true,
  },
  /**窗口单行字符数 */
  cols: {
    type: Number,
    default: 120,
  },
  /**窗口行数 */
  rows: {
    type: Number,
    default: 128,
  },
  /**禁止输入 */
  disable: {
    type: Boolean,
    default: false,
  },
  /**初始发送命令 */
  initCmd: {
    type: [String, Boolean],
    default: false,
  },
});

/**终端输入DOM节点实例对象 */
const terminalDom = ref<HTMLElement | undefined>(undefined);

/**终端输入实例对象 */
const terminal = ref<any>(null);

/**终端输入文字状态 */
const terminalState = reactive<{
  /**输入值 */
  text: string;
  /**历史 */
  history: {
    label?: string;
    value: string;
  }[];
}>({
  text: '',
  history: [
    {
      value: 'help',
    },
    {
      value: 'quit',
    },
    {
      value: 'list ver',
    },
    {
      value: 'list lic',
    },
  ],
});

/**自动完成根据输入项进行筛选 */
function fnAutoCompleteFilter(input: string, option: any) {
  return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0;
}

/**自动完成按键触发 */
function fnAutoCompleteKeydown(evt: KeyboardEvent) {
  if (evt.key === 'Enter') {
    // 阻止默认的换行行为
    evt.preventDefault();
    // 按下 Shift + Enter 键时换行
    if (evt.shiftKey && evt.target) {
      // 插入换行符
      const textarea = evt.target as HTMLInputElement;
      const start = textarea.selectionStart || 0;
      const end = textarea.selectionEnd || 0;
      const text = textarea.value;
      textarea.value = text.substring(0, start) + '\n' + text.substring(end);
      terminalState.text = textarea.value;
      // 更新光标位置
      textarea.selectionStart = textarea.selectionEnd = start + 1;
    } else {
      // ws未连接
      if (ws.state() !== WebSocket.OPEN) {
        message.error('disconnected');
        return;
      }

      // 输入历史
      const cmdStr = terminalState.text.trim().replace(/\n/g, '\r\n');
      const hisIndex = terminalState.history.findIndex(
        item => item.value === cmdStr
      );
      if (hisIndex === -1) {
        terminalState.history.push({
          value: cmdStr,
        });
      }

      // 发送文本
      terminal.value.scrollToBottom();
      terminal.value.writeln(cmdStr);
      ws.send({
        requestId: `telnet_${props.hostId}`,
        type: 'telnet',
        data: `${cmdStr}\r\n`,
      });
      terminalState.text = '';

      // 退出登录
      if (['q', 'quit', 'exit'].includes(cmdStr)) {
        setTimeout(() => {
          ws.close();
        }, 1000);
      }
    }
  }
}

/**终端输入渲染 */
function handleRanderXterm(container: HTMLElement | undefined) {
  if (!container) return;
  const xterm = new Terminal({
    lineHeight: 1.2,
    fontSize: 12,
    fontFamily: "Monaco, Menlo, Consolas, 'Courier New', monospace",
    theme: {
      background: '#000000',
    },
    cursorBlink: true, // 光标闪烁
    cursorStyle: 'block',
    scrollback: 1000,
    scrollSensitivity: 15,
    tabStopWidth: 4,
    disableStdin: props.disable, // 禁止输入
  });
  // 挂载
  xterm.open(container);
  // 自适应尺寸
  const fitAddon = new FitAddon();
  xterm.loadAddon(fitAddon);
  // 终端尺寸变化触发
  xterm.onResize(({ cols, rows }) => {
    // console.log('尺寸', cols, rows);
    ws.send({
      requestId: `telnet_resize_${props.hostId}`,
      type: 'resize',
      data: { cols, rows },
    });
  });

  // 创建 ResizeObserver 实例
  var observer = new ResizeObserver(entries => {
    fitAddon.fit();
  });
  // 监听元素大小变化
  observer.observe(container);

  terminal.value = xterm;
}

/**连接打开后回调 */
function wsOpen(ev: any) {
  // console.info('wsOpen', ev);
  nextTick(() => {
    handleRanderXterm(terminalDom.value);

    // 连接事件
    emit('connect', {
      timeStamp: ev.timeStamp,
      cols: terminal.value.cols,
      rows: terminal.value.rows,
      hostId: props.hostId,
      id: props.id,
    });
    // 初始发送命令
    if (typeof props.initCmd === 'string') {
      ws.send({
        requestId: `telnet_${props.hostId}`,
        type: 'telnet',
        data: `${props.initCmd}\r\n`,
      });
    }
  });
}

/**连接错误后回调 */
function wsError(ev: any) {
  console.error('wsError', ev);
  if (terminal.value != null) {
    let message = 'disconnected';
    terminal.value.write(`\x1b[31m${message}\x1b[m\r\n`);
  } else if (terminalDom.value) {
    terminalDom.value.style.background = '#000';
    terminalDom.value.style.color = '#ff4d4f';
    terminalDom.value.style.height = '60%';
    terminalDom.value.innerText = 'disconnected';
  }
}

/**连接关闭后回调 */
function wsClose(code: number) {
  // console.warn('wsClose', code);
  if (terminal.value != null) {
    let message = 'disconnected';
    terminal.value.write(`\x1b[31m${message}\x1b[m\r\n`);
  }
  // 关闭事件
  emit('close', {
    code: code,
    hostId: props.hostId,
    id: props.id,
  });
}

/**接收消息后回调 */
function wsMessage(res: Record<string, any>) {
  emit('message', res);
  // console.log('wsMessage', res);
  const { code, requestId, data } = res;
  if (code === RESULT_CODE_ERROR) {
    console.warn(res.msg);
    return;
  }
  if (!requestId) return;
  if (terminal.value != null) {
    // terminal.value.write(data.trim().replace(/\n/g, "\r\n"));
    // 是否n结尾
    if (/[\r\n]$/.test(data)) {
      terminal.value.writeln(data.trim().replace(/\n/g, '\r\n'));
    } else {
      terminal.value.write(data.replace(/\n/g, '\r\n'));
    }
  }
}

onMounted(() => {
  if (props.hostId) {
    // 建立链接
    const options: OptionsType = {
      url: '/ws/telnet',
      params: {
        hostId: props.hostId,
        cols: props.cols,
        rows: props.rows,
      },
      onmessage: wsMessage,
      onerror: wsError,
      onopen: wsOpen,
      onclose: wsClose,
    };
    ws.connect(options);
  }
});

onBeforeUnmount(() => {
  ws.close();
});

// 给组件设置属性 ref="xxxTerminal"
// setup内使用 const xxxTerminal = ref();
defineExpose({
  /**发送方法 */
  send: (data: string) => {
    ws.send({
      requestId: `telnet_${props.hostId}`,
      type: 'telnet',
      data: `${data}\r\n`,
    });
  },
});
</script>

<template>
  <div class="terminal">
    <div ref="terminalDom" style="height: calc(100% - 36px)" :id="id"></div>
    <a-auto-complete
      v-model:value="terminalState.text"
      :dropdown-match-select-width="500"
      style="width: 100%"
      :options="terminalState.history"
      :filter-option="fnAutoCompleteFilter"
      :defaultActiveFirstOption="false"
    >
      <a-textarea
        :auto-size="{ minRows: 1, maxRows: 6 }"
        placeholder="Execute command. Shift+Enter to line feed, Enter to send"
        @keypress="fnAutoCompleteKeydown"
      />
    </a-auto-complete>
  </div>
</template>

<style lang="css" scoped>
.terminal {
  width: 100%;
  height: 100%;
}
</style>
