<script setup>
import {onMounted, reactive, ref, watch} from "vue";
import useClipboard from 'vue-clipboard3';

// import.meta.env
let ws = new WebSocket(`${import.meta.env.VITE_APP_SOCKET_BASE_URl}`);

ws.onerror = (event) => {
  console.error("ws链接错误：", event);
}
ws.onopen = (event) => {
  heartCheck.reset().start();
  console.log("ws链接成功：", event)
}

ws.onmessage = (messageEvent) => {
  heartCheck.reset().start();
  const message = JSON.parse(messageEvent.data);
  console.log(message)
  switch (message.messageType) {
    case "heartbeat":
      if (message.status) {
        state.sessionId = message.id;
      }else {
        ws = new WebSocket(`${import.meta.env.VITE_APP_SOCKET_BASE_URl}`);
        return
      }
      if (message.onlineNumber) {
        state.onlineNumber = message.onlineNumber;
      }
      break
    case "receive":
      state.messageList.push({
        ...message
      })
      setTimeout( () => {
        const div = document.querySelector(".message-list");
        div.scrollTop = div.scrollHeight;
      }, 100)
      break
    case "refresh":
      getMessageList()
  }
}

ws.onclose = (event) => {
  console.log("ws链接断开：", event)
}

// 心跳检测, 每隔一段时间检测连接状态，如果处于连接中，就向server端主动发送消息，来重置server端与客户端的最大连接时间，如果已经断开了，发起重连。
const heartCheck = {
  timeout: 30000,
  serverTimeoutObj: null,
  reset: function () {
    clearTimeout(this.serverTimeoutObj);
    return this;
  },
  start: function () {
    this.serverTimeoutObj = setInterval(function () {
      if (ws.readyState === 1) {
        ws.send(JSON.stringify({
          messageType:'heartbeat',
          sessionId: state.sessionId
        }));
        heartCheck.reset().start();    // 如果获取到消息，说明连接是正常的，重置心跳检测
      } else {
        ws = new WebSocket("/socket/message");
      }
    }, this.timeout)
  }
};

const rightMenuVisible = ref(false);
const state = reactive({
  sessionId:"",
  messageList:[],
  message:"",
  visible:false,
  left:0,
  top:0,
  rightMenuItem:{},
  onlineNumber:0,
  showModal:false,
  modalImageUUID:'',
})

onMounted(() => {
  getMessageList()
})

const getMessageList = () => {
  let url = '/papi/message/get';
  let options = {
    method: 'GET',
  };
  fetch(url, options)
      .then(res => res.json())
      .then(json => {
        state.messageList = json.data
        setTimeout( () => {
          const div = document.querySelector(".message-list");
          if (div)  {
            div.scrollTop = div.scrollHeight;
          }

        }, 200)
      })
      .catch(err => console.error('error:' + err));

}

const handleSendMessage = () => {
  const message = {
    "messageType":"send",
    "sessionId": state.sessionId,
    "content": state.message,
    "type": "text",
    "time": Date.now()
  }
  ws.send(JSON.stringify(message))
  state.message = ""
}

const handleDeleteMsg = () => {
  const message = {
    ...state.rightMenuItem,
    sessionId:state.sessionId,
    messageType: 'delete',
    isDelete:1
  }
  const index = state.messageList.findIndex(item => item.id === state.rightMenuItem.id)
  state.messageList.splice(index, 1)
  ws.send(JSON.stringify(message))
}

const keyDown = (e) => {
  if(e.ctrlKey && e.keyCode === 13) {   //用户点击了ctrl+enter触发
    state.message += '\n';
  }else if (state.message.trim().length > 0){ //用户点击了enter触发
    handleSendMessage();
  }
  e.preventDefault();
}

const formatDate = (dateForm) => {
  if (dateForm === "") {
    return "";
  }else{
    return new Date(+new Date(new Date(dateForm).toJSON()) + 8 * 3600 * 1000).toISOString().replace(/T/g, ' ').replace(/\.[\d]{3}Z/, '');
  }
}

const openMenu = (event, item) => {
  rightMenuVisible.value = true;
  state.top = event.pageY;
  state.left = event.pageX;
  state.rightMenuItem = item
}

watch(rightMenuVisible, (newVisible) => {
  if (newVisible) {
    document.body.addEventListener('click', () => {rightMenuVisible.value = false})
    document.querySelector(".message-list").addEventListener('scroll', () => {rightMenuVisible.value = false})
  } else {
    document.body.removeEventListener('click', () => {rightMenuVisible.value = false})
  }
})

const handleFileUpload = (event) => {
  const file = event.target.files[0];
  uploadFileMessage(file)
}

const handleInputPaste = (event) => {
  if (event.clipboardData.files.length === 0) {
    return
  }
  event.preventDefault();
  const file = event.clipboardData.files[0];
  uploadFileMessage(file)
}

const uploadFileMessage = (file) => {
  if (file.name.indexOf(".") === -1) {
    window.alert("该类型文件禁止上传！")
    return
  }
  if (file.size >= 1024 * 1024 * 20) {
    window.alert("文件上传最大20M！")
    return
  }
  const formData = new FormData();
  formData.append('file', file);

  fetch('/papi/file/upload', {
    method: 'POST',
    body: formData
  }).then(res => res.json())
      .then(json => {
        console.log(json)
        const uuid = json.uuid;
        const message = {
          "messageType":"send",
          "sessionId": state.sessionId,
          "type": "file",
          "fileMimeType": json.fileMimeType,
          "time": Date.now(),
          "file": uuid
        }
        ws.send(JSON.stringify(message))
      })
      .catch(err => console.error('error:' + err));
}

const handleInputDrop = (event) => {
  event.preventDefault();
  if (event.dataTransfer.files.length === 0) {
    return
  }
  const file = event.dataTransfer.files[0];
  uploadFileMessage(file)
}

const isImageFile = (fileMimetype) => {
  if (fileMimetype) {
    return fileMimetype.startsWith("image/");
  }
  return false;
}

const isVideoFile = (fileMimetype) => {
  if (fileMimetype) {
    return fileMimetype.startsWith("video/");
  }
  return false;
}

const isAudioFile = (fileMimetype) => {
  if (fileMimetype) {
    return fileMimetype.startsWith("audio/");
  }
  return false;
}

const downloadFile = (message) => {
  message ? window.open('/papi/file/' + message.file, '_blank') : window.open('/papi/file/' + state.rightMenuItem.file, '_blank')
}

const getIconUrl = (message) => {
  const baseUrl = '/images/'
  if (message.fileMimeType.indexOf('doc') !== -1) {
    return new URL(baseUrl + 'doc.png', import.meta.url).href
  } else if (message.fileMimeType.indexOf('pdf') !== -1) {
    return new URL(baseUrl + 'pdf.png', import.meta.url).href
  } else if (message.fileMimeType.indexOf('xls') !== -1) {
    return new URL(baseUrl + 'xls.png', import.meta.url).href
  } else{
    return new URL(baseUrl + 'default.png', import.meta.url).href
  }
}

const openModal = (message) => {
  console.log(message)
  state.showModal = true;
  state.modalImageUUID = message.file
}

</script>

<template>
  <div class="chat">
    <div class="chat-header">
      <h3>文件传输助手</h3>
      <p>在线人数：{{ state.onlineNumber }}</p>
    </div>
    <div class="chat-body">
      <p v-if="state.messageList.length === 0">你可以使用「手机微信>文件传输助手」与电脑互传文件</p>
      <div class="message-list">
        <div :class="message.sessionId != null && message.sessionId === state.sessionId ? 'message-content message-content-right' : 'message-content'" v-for="message in state.messageList"
             @contextmenu.prevent="openMenu($event, message)">
          <span v-if="message.sessionId != null && message.sessionId === state.sessionId" class="message-text-time">{{formatDate(message.time)}}</span>
          <div :class="message.sessionId != null && message.sessionId === state.sessionId ? 'message-text-right' : 'message-text-left'"
               class="message-text"
               v-if="message.type === 'text'"
               v-html="message.content"
          ></div>
          <div :class="message.sessionId != null && message.sessionId === state.sessionId ? 'message-text-right' : 'message-text-left'"
               class="message-text"
               v-if="message.type === 'file'">
            <img v-if="isImageFile(message.fileMimeType)" @click="openModal(message)" :src="'/papi/file/' + message.file" alt="picture"/>
            <video width="300" controls v-else-if="isVideoFile(message.fileMimeType)" :src="'/papi/file/' + message.file"/>
            <audio preload width="300" controls v-else-if="isAudioFile(message.fileMimeType)" :src="'/papi/file/' + message.file"/>
            <div v-else class="message-file" @click="downloadFile(message)">
              <img width="60" :src="getIconUrl(message)" :alt="getIconUrl(message)">
              <span v-html="message.fileName"></span>
            </div>
          </div>
          <span v-if="message.sessionId == null || message.sessionId !== state.sessionId" class="message-text-time">{{formatDate(message.time)}}</span>
        </div>
        <div v-show="rightMenuVisible" :style="{left:state.left+'px',top:state.top+'px'}" class="right-menu">
          <button v-if="state.rightMenuItem?.type === 'text'" @click="useClipboard().toClipboard(state.rightMenuItem.content)">复制</button>
          <button v-if="state.rightMenuItem?.type === 'file' || state.rightMenuItem?.type === 'image'" @click="downloadFile(null)">下载</button>
          <button @click="handleDeleteMsg">删除</button>
        </div>

      </div>
    </div>
    <div class="chat-input">
      <div class="chat-input-operation">
        <div class="operation-item">
          <input class="file-input" title="上传文件"  type="file" @change="handleFileUpload">
          <img class="file-upload" src="/src/assets/file.svg" alt="file"/>
        </div>
      </div>
      <textarea @drop="handleInputDrop" placeholder="回车发送消息，ctrl+enter换行，支持粘贴、拖拽上传附件" @paste="handleInputPaste" rows="4" @keydown.enter="keyDown" v-model="state.message"/>
      <div class="message-send">
        <button @click="handleSendMessage" :disabled="state.message.length === 0">发送</button>
      </div>
    </div>
  </div>
  <div v-show="state.showModal" class="modal" @click="state.showModal = false">
    <img v-if="state.modalImageUUID !== ''" class="modal-image" :src="'/papi/file/' + state.modalImageUUID" alt="message"/>
  </div>
</template>

<style scoped>
.chat {
  height: 90vh;
  display: flex;
  flex-direction: column;
  background-color: #ffffff;
  border-radius: 4px;
  margin: 0 auto;
  max-width: 960px;
  transition: width .2s ease-in;

  width: 960px;
}
.chat-body {
  height: 68vh;
}
.message-list {
  height: 100%;
  overflow-y: auto;
  scrollbar-gutter: stable;
}
.chat-body p {
  align-items: center;
  color: #9e9e9e;
  display: flex;
  font-size: 14px;
  height: 100%;
  justify-content: center;
}
.chat-input {
  border-top: 1px solid #e3e4e5;
  display: flex;
  flex-direction: column;
}
.chat-header {
  border-bottom: 1px solid #e3e4e5;
  padding: 0 24px;
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.chat-input-operation {
  display: flex;
  padding: 10px 16px;
}
.file-input {
  font-size: 0;
  height: 100%;
  opacity: 0;
  position: absolute;
  width: 100%;
  cursor: pointer;
}
.operation-item {
  display: inline-block;
  height: 15px;
  position: relative;
  width: 17px;
}
textarea {
  border: none;
  flex: 1;
  font-size: 14px;
  padding: 0 18px;
  resize: none;
  outline: none;
  color:black;
  background-color: #ffffff;
  max-height: 10vh;
}
button {
  outline: none;
}
.message-send {
  display: flex;
  justify-content: flex-end;
  padding: 8px 16px 8px;
}
.message-content {
  display: flex;
  padding: 8px 24px 8px;
  gap: 14px;
}
.message-text {
  background-color: #95ec69;
  max-width: 400px;
  text-align: left;
  border-radius: 4px;
  box-sizing: border-box;
  padding: 7px 12px;
  position: relative;
  height: fit-content;

  font-family: monospace;
  unicode-bidi: isolate;
  white-space: break-spaces;
  word-break: break-word;
  min-height: 35px;
}
.message-text:before {
  background-color: #95ec69;
  border-radius: 0 2px 0 0;
  content: "";
  height: 10px;
  margin-right: -5px;
  position: absolute;
  right: 0;
  top: 12px;
  transform: rotate(45deg);
  transform-origin: 50%;
  width: 10px;
}
.message-text-left:before {
  left: 0;
  margin-left: -5px;
}
.message-content-right {
  justify-content: flex-end;
}
.message-text-time {
  font-size: 12px;
  color: #999;
  display: flex;
  align-items: flex-end;
}

.right-menu {
  border: 1px solid #eee;
  border-radius: 10px;
  z-index: 999;
  position: fixed;
  background: #fff;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 2px;
}

@media (max-width: 960px) {
  .chat {
    width: 80vw !important;
  }
}
.message-list img {
  max-width: 376px;
  cursor: zoom-in;
}
.message-file {
  display: flex;
  gap: 12px;
  flex-direction: row;
  align-items: flex-end;

  cursor: pointer;
}

.modal {
  position: fixed;
  z-index: 9999;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.8);
}

.modal-image {
  display: flex;
  max-width: 90%;
  margin: 5vh auto;
  height: 90vh;
  object-fit: contain;
  cursor: zoom-out;
}

</style>
