<template>
  <div class="app-container vet-online-consult">
    <el-card shadow="never" class="vet-online-consult__toolbar">
      <el-form :inline="true" size="small" @submit.native.prevent>
        <el-form-item :label="dtT('queryAskerName')">
          <el-input
            v-model="filterAskerName"
            :placeholder="dtT('queryAskerNamePh')"
            clearable
            style="width: 200px"
            @keyup.enter.native="handleSearch"
          />
        </el-form-item>
        <el-form-item :label="dtT('queryContent')">
          <el-input
            v-model="filterContent"
            :placeholder="dtT('queryContentPh')"
            clearable
            style="width: 220px"
            @keyup.enter.native="handleSearch"
          />
        </el-form-item>
        <el-form-item>
          <el-button type="primary" icon="el-icon-search" @click="handleSearch">{{ dtCommon("search") }}</el-button>
          <el-button icon="el-icon-refresh" @click="handleReset">{{ dtCommon("reset") }}</el-button>
        </el-form-item>
      </el-form>
    </el-card>

    <el-card shadow="never" class="vet-online-consult__main">
      <div class="vet-online-consult__layout">
        <aside class="vet-online-consult__sessions">
          <div class="vet-online-consult__sessions-head">
            <div class="vet-online-consult__sessions-title">{{ dtT("listTitle") }}</div>
            <div class="vet-online-consult__sessions-sub">{{ dtT("listSubDefault") }}</div>
          </div>
          <div v-loading="listLoading" class="vet-online-consult__sessions-scroll">
            <div
              v-for="s in sessions"
              :key="s.id"
              class="session-item"
              :class="{ 'is-active': s.id === activeSessionId }"
              @click="openSession(s)"
            >
              <el-avatar :size="40" :src="avatarUrl(s.askerAvatar)" icon="el-icon-user-solid" class="session-item__avatar" />
              <div class="session-item__text">
                <div class="session-item__row">
                  <span class="session-item__name">{{ s.askerName || dash }}</span>
                  <span class="session-item__date">{{ formatListDate(s.lastMessageTime) }}</span>
                </div>
                <div class="session-item__preview">{{ s.lastMessagePreview || dash }}</div>
              </div>
            </div>
            <el-empty
              v-if="!listLoading && !sessions.length"
              :description="listEmptyText"
              :image-size="72"
            />
          </div>
        </aside>

        <section class="vet-online-consult__chat">
          <header class="vet-online-consult__chat-head">
            {{ activeSession ? (activeSession.askerName || dash) : dtT("selectSession") }}
          </header>
          <div
            ref="chatScroll"
            v-loading="messagesLoading"
            class="vet-online-consult__chat-body"
            @scroll.passive="onChatScroll"
          >
            <div v-if="activeSessionId && loadingOlder" class="chat-load-hint">{{ dtT("loadingMore") }}</div>
            <div v-if="activeSessionId && noMoreOlder && messages.length" class="chat-load-hint">{{ dtT("noMoreHistory") }}</div>
            <div class="chat-messages">
              <template v-if="activeSessionId && messages.length">
                <template v-for="(group, gi) in messageGroups">
                  <div :key="'d-' + gi" class="chat-date-divider">{{ group.dateLabel }}</div>
                  <div
                    v-for="m in group.items"
                    :key="m.id"
                    class="chat-row"
                    :class="isVetMessage(m) ? 'is-vet' : 'is-asker'"
                  >
                    <el-avatar
                      v-if="!isVetMessage(m)"
                      :size="36"
                      :src="avatarUrl(activeSession && activeSession.askerAvatar)"
                      icon="el-icon-user-solid"
                      class="chat-row__avatar"
                    />
                    <div class="chat-bubble" :class="isVetMessage(m) ? 'is-vet' : 'is-asker'">
                      <div v-if="m.msgType === 1" class="chat-bubble__text">{{ m.content }}</div>
                      <el-image
                        v-else-if="m.msgType === 2"
                        :src="mediaUrl(m.content)"
                        :preview-src-list="[mediaUrl(m.content)]"
                        fit="cover"
                        class="chat-bubble__img"
                      />
                      <video
                        v-else-if="m.msgType === 3"
                        :src="mediaUrl(m.content)"
                        controls
                        class="chat-bubble__video"
                      />
                      <div
                        v-else-if="m.msgType === 4"
                        class="voice-msg"
                        :class="{ 'is-playing': playingVoiceId === m.id }"
                        @click="toggleVoice(m)"
                      >
                        <span class="voice-msg__dur">{{ formatVoiceDuration(m.mediaDuration) }}</span>
                        <span class="voice-msg__waves"><i /><i /><i /></span>
                      </div>
                      <div v-else class="chat-bubble__text">{{ m.content }}</div>
                      <div class="chat-bubble__time">{{ formatMsgTime(m.sendTime) }}</div>
                    </div>
                    <el-avatar
                      v-if="isVetMessage(m)"
                      :size="36"
                      icon="el-icon-s-custom"
                      class="chat-row__avatar"
                    />
                  </div>
                </template>
              </template>
              <el-empty
                v-else-if="activeSessionId && !messagesLoading"
                :description="dtT('emptyChat')"
                :image-size="80"
              />
              <el-empty v-else-if="!activeSessionId" :description="dtT('selectSession')" :image-size="80" />
            </div>
          </div>

          <footer v-if="activeSessionId" class="vet-online-consult__composer">
            <div class="composer-tools">
              <el-upload
                class="composer-upload"
                :action="uploadAction"
                :headers="uploadHeaders"
                :show-file-list="false"
                accept=".jpg,.jpeg,.png,.gif"
                :before-upload="(f) => beforeMediaUpload(f, 'image')"
                :on-success="(res, file) => onMediaUploadSuccess(res, file, 2)"
                :on-error="onUploadError"
              >
                <el-button type="text" icon="el-icon-picture-outline" :title="dtT('uploadImage')" />
              </el-upload>
              <el-upload
                class="composer-upload"
                :action="uploadAction"
                :headers="uploadHeaders"
                :show-file-list="false"
                accept=".mp4,.mov"
                :before-upload="(f) => beforeMediaUpload(f, 'video')"
                :on-success="(res, file) => onMediaUploadSuccess(res, file, 3)"
                :on-error="onUploadError"
              >
                <el-button type="text" icon="el-icon-video-camera" :title="dtT('uploadVideo')" />
              </el-upload>
              <el-upload
                class="composer-upload"
                :action="uploadAction"
                :headers="uploadHeaders"
                :show-file-list="false"
                accept=".mp3,.m4a,.wav"
                :before-upload="(f) => beforeMediaUpload(f, 'voice')"
                :on-success="(res, file) => onMediaUploadSuccess(res, file, 4)"
                :on-error="onUploadError"
              >
                <el-button type="text" icon="el-icon-microphone" :title="dtT('uploadVoice')" />
              </el-upload>
            </div>
            <div class="composer-input-row">
              <el-input
                v-model="draft"
                type="textarea"
                :rows="3"
                resize="none"
                :placeholder="dtT('draftPlaceholder')"
                :disabled="sending"
                @keydown.native="onDraftKeydown"
              />
              <el-button
                type="primary"
                class="composer-send"
                :loading="sending"
                :disabled="sendDisabled"
                @click="sendText"
              >{{ dtT("send") }}</el-button>
            </div>
          </footer>
        </section>
      </div>
    </el-card>
  </div>
</template>

<script>
import { mapGetters } from "vuex"
import { getToken } from "@/utils/auth"
import diseaseTreatmentLocaleMixin from "@/mixins/diseaseTreatmentLocaleMixin"
import {
  listVetConsultSessions,
  listVetConsultMessages,
  sendVetConsultMessage
} from "@/api/diseaseTreatment/vetOnlineConsult"

const SENDER_ROLE_ASKER = 1
const SENDER_ROLE_VET = 2
const POLL_MS = 2000
const MESSAGE_PAGE_SIZE = 50

const MEDIA_RULES = {
  image: { exts: ["jpg", "jpeg", "png", "gif"], maxMb: 10, errFmt: "errImageFmt", errMb: "errImageMb" },
  video: { exts: ["mp4", "mov"], maxMb: 50, errFmt: "errVideoFmt", errMb: "errVideoMb" },
  voice: { exts: ["mp3", "m4a", "wav"], maxMb: 10, errFmt: "errVoiceFmt", errMb: "errVoiceMb" }
}

export default {
  name: "DiseaseTreatmentVetOnlineConsult",
  mixins: [diseaseTreatmentLocaleMixin],
  data() {
    return {
      dtNs: "vetOnlineConsult",
      listLoading: false,
      messagesLoading: false,
      loadingOlder: false,
      noMoreOlder: false,
      sending: false,
      searchMode: false,
      filterAskerName: "",
      filterContent: "",
      sessions: [],
      activeSessionId: null,
      messages: [],
      draft: "",
      pollTimer: null,
      playingVoiceId: null,
      voiceAudio: null,
      listEmptyHint: ""
    }
  },
  computed: {
    ...mapGetters(["id", "name", "avatar"]),
    dash() {
      return this.$t("diseaseTreatment.status.dash")
    },
    activeSession() {
      return this.sessions.find((s) => s.id === this.activeSessionId) || null
    },
    listEmptyText() {
      return this.listEmptyHint || this.dtT("emptySessions")
    },
    messageGroups() {
      const groups = []
      let currentKey = null
      let currentItems = []
      for (const m of this.messages) {
        const key = this.dateKey(m.sendTime)
        if (key !== currentKey) {
          if (currentItems.length) {
            groups.push({ dateLabel: this.formatDateDivider(currentKey), items: currentItems })
          }
          currentKey = key
          currentItems = [m]
        } else {
          currentItems.push(m)
        }
      }
      if (currentItems.length) {
        groups.push({ dateLabel: this.formatDateDivider(currentKey), items: currentItems })
      }
      return groups
    },
    uploadAction() {
      return process.env.VUE_APP_BASE_API + "/common/upload"
    },
    uploadHeaders() {
      return { Authorization: "Bearer " + getToken() }
    },
    sendDisabled() {
      return this.sending || !(this.draft || "").trim()
    }
  },
  created() {
    this.loadSessions(false)
  },
  beforeDestroy() {
    this.stopPolling()
    this.stopVoice()
  },
  methods: {
    isVetMessage(m) {
      return m && m.senderRole === SENDER_ROLE_VET
    },
    avatarUrl(url) {
      if (!url) {
        return ""
      }
      if (/^https?:\/\//i.test(url)) {
        return url
      }
      return process.env.VUE_APP_BASE_API + url
    },
    mediaUrl(url) {
      return this.avatarUrl(url)
    },
    formatListDate(time) {
      if (!time) {
        return ""
      }
      const d = new Date(time)
      if (isNaN(d.getTime())) {
        return ""
      }
      return `${d.getMonth() + 1}月${d.getDate()}日`
    },
    formatMsgTime(time) {
      if (!time) {
        return ""
      }
      const d = new Date(time)
      if (isNaN(d.getTime())) {
        return ""
      }
      const h = String(d.getHours()).padStart(2, "0")
      const min = String(d.getMinutes()).padStart(2, "0")
      return `${h}:${min}`
    },
    dateKey(time) {
      if (!time) {
        return ""
      }
      const d = new Date(time)
      if (isNaN(d.getTime())) {
        return ""
      }
      return `${d.getFullYear()}-${d.getMonth() + 1}-${d.getDate()}`
    },
    formatDateDivider(key) {
      if (!key) {
        return ""
      }
      const parts = key.split("-")
      if (parts.length >= 3) {
        return `${Number(parts[1])}月${Number(parts[2])}日`
      }
      return key
    },
    formatVoiceDuration(sec) {
      const s = Math.max(1, Math.round(Number(sec) || 1))
      return s + '"'
    },
    loadSessions(searchMode) {
      this.listLoading = true
      const params = {
        pageNum: 1,
        pageSize: 100,
        askerName: (this.filterAskerName || "").trim() || undefined,
        contentKeyword: (this.filterContent || "").trim() || undefined,
        searchMode: searchMode ? true : undefined
      }
      listVetConsultSessions(params)
        .then((res) => {
          this.sessions = res.rows || []
          if (searchMode && !this.sessions.length) {
            this.listEmptyHint = this.dtT("noSearchResult")
          } else {
            this.listEmptyHint = this.dtT("emptySessions")
          }
          if (this.activeSessionId && !this.sessions.some((s) => s.id === this.activeSessionId)) {
            this.activeSessionId = null
            this.messages = []
            this.stopPolling()
          }
        })
        .catch(() => {
          this.sessions = []
        })
        .finally(() => {
          this.listLoading = false
        })
    },
    handleSearch() {
      const asker = (this.filterAskerName || "").trim()
      const content = (this.filterContent || "").trim()
      if (!asker && !content) {
        this.$modal.msgWarning(this.dtT("searchNeedFilter"))
        return
      }
      this.searchMode = true
      this.loadSessions(true)
    },
    handleReset() {
      this.filterAskerName = ""
      this.filterContent = ""
      this.searchMode = false
      this.loadSessions(false)
    },
    openSession(session) {
      if (!session || !session.id) {
        return
      }
      this.activeSessionId = session.id
      this.noMoreOlder = false
      this.messages = []
      this.loadMessages(false)
      this.startPolling()
    },
    loadMessages(older) {
      if (!this.activeSessionId) {
        return
      }
      const beforeId = older && this.messages.length ? this.messages[0].id : undefined
      if (older) {
        this.loadingOlder = true
      } else {
        this.messagesLoading = true
      }
      listVetConsultMessages(this.activeSessionId, {
        beforeId,
        pageSize: MESSAGE_PAGE_SIZE
      })
        .then((res) => {
          const batch = res.data || []
          if (older) {
            if (!batch.length) {
              this.noMoreOlder = true
            } else {
              const el = this.$refs.chatScroll
              const prevHeight = el ? el.scrollHeight : 0
              this.messages = batch.concat(this.messages)
              this.$nextTick(() => {
                if (el) {
                  el.scrollTop = el.scrollHeight - prevHeight
                }
              })
              if (batch.length < MESSAGE_PAGE_SIZE) {
                this.noMoreOlder = true
              }
            }
          } else {
            this.messages = batch
            this.$nextTick(() => this.scrollChatBottom())
            if (!batch.length) {
              this.noMoreOlder = true
            }
          }
        })
        .finally(() => {
          this.messagesLoading = false
          this.loadingOlder = false
        })
    },
    refreshMessagesQuiet() {
      if (!this.activeSessionId) {
        return
      }
      listVetConsultMessages(this.activeSessionId, { pageSize: MESSAGE_PAGE_SIZE })
        .then((res) => {
          const batch = res.data || []
          const lastId = this.messages.length ? this.messages[this.messages.length - 1].id : null
          const newLastId = batch.length ? batch[batch.length - 1].id : null
          if (newLastId !== lastId || batch.length !== this.messages.length) {
            const el = this.$refs.chatScroll
            const atBottom = el ? el.scrollHeight - el.scrollTop - el.clientHeight < 80 : true
            this.messages = batch
            if (atBottom) {
              this.$nextTick(() => this.scrollChatBottom())
            }
          }
        })
        .catch(() => {})
    },
    refreshSessionListQuiet() {
      const params = {
        pageNum: 1,
        pageSize: 100,
        askerName: (this.filterAskerName || "").trim() || undefined,
        contentKeyword: (this.filterContent || "").trim() || undefined,
        searchMode: this.searchMode ? true : undefined
      }
      listVetConsultSessions(params)
        .then((res) => {
          this.sessions = res.rows || []
        })
        .catch(() => {})
    },
    onChatScroll(e) {
      const el = e.target
      if (el.scrollTop < 40 && !this.loadingOlder && !this.noMoreOlder && this.messages.length) {
        this.loadMessages(true)
      }
    },
    startPolling() {
      this.stopPolling()
      this.pollTimer = setInterval(() => {
        this.refreshMessagesQuiet()
        this.refreshSessionListQuiet()
      }, POLL_MS)
    },
    stopPolling() {
      if (this.pollTimer) {
        clearInterval(this.pollTimer)
        this.pollTimer = null
      }
    },
    scrollChatBottom() {
      const el = this.$refs.chatScroll
      if (!el) {
        return
      }
      el.scrollTop = el.scrollHeight
    },
    onDraftKeydown(e) {
      if (e.key === "Enter" && !e.shiftKey) {
        e.preventDefault()
        if (!this.sendDisabled) {
          this.sendText()
        }
      }
    },
    sendText() {
      const text = (this.draft || "").trim()
      if (!text) {
        this.$modal.msgWarning(this.dtT("inputRequired"))
        return
      }
      this.doSend({ msgType: 1, content: text })
    },
    doSend(payload) {
      if (!this.activeSessionId || this.sending) {
        return
      }
      this.sending = true
      sendVetConsultMessage(this.activeSessionId, payload)
        .then((res) => {
          const msg = res.data
          if (msg && msg.id) {
            const exists = this.messages.some((m) => m.id === msg.id)
            if (!exists) {
              this.messages.push(msg)
            }
          }
          this.draft = ""
          this.$nextTick(() => this.scrollChatBottom())
          this.refreshSessionListQuiet()
        })
        .catch((err) => {
          const m = (err && err.message) || this.dtT("sendFail")
          this.$modal.msgError(m)
        })
        .finally(() => {
          this.sending = false
        })
    },
    extOf(fileName) {
      if (!fileName || fileName.lastIndexOf(".") < 0) {
        return ""
      }
      return fileName.slice(fileName.lastIndexOf(".") + 1).toLowerCase()
    },
    beforeMediaUpload(file, kind) {
      const rule = MEDIA_RULES[kind]
      const ext = this.extOf(file.name)
      if (!rule.exts.includes(ext)) {
        this.$modal.msgError(this.dtT(rule.errFmt))
        return false
      }
      if (file.name.includes(",")) {
        this.$modal.msgError(this.dtT("errComma"))
        return false
      }
      if (file.size / 1024 / 1024 >= rule.maxMb) {
        this.$modal.msgError(this.dtT(rule.errMb))
        return false
      }
      this.$modal.loading(this.dtT("uploading"))
      return true
    },
    onMediaUploadSuccess(res, file, msgType) {
      this.$modal.closeLoading()
      if (res.code === 200 && (res.url || res.fileName)) {
        this.doSend({
          msgType,
          content: res.url || res.fileName
        })
      } else {
        this.$modal.msgError(res.msg || this.dtT("uploadFail"))
      }
    },
    onUploadError() {
      this.$modal.closeLoading()
      this.$modal.msgError(this.dtT("uploadFail"))
    },
    stopVoice() {
      if (this.voiceAudio) {
        this.voiceAudio.pause()
        this.voiceAudio = null
      }
      this.playingVoiceId = null
    },
    toggleVoice(m) {
      if (!m || !m.content) {
        return
      }
      if (this.playingVoiceId === m.id) {
        this.stopVoice()
        return
      }
      this.stopVoice()
      this.playingVoiceId = m.id
      const audio = new Audio(this.mediaUrl(m.content))
      this.voiceAudio = audio
      audio.onended = () => {
        if (this.playingVoiceId === m.id) {
          this.stopVoice()
        }
      }
      audio.onerror = () => {
        this.$modal.msgError(this.dtT("voicePlayFail"))
        this.stopVoice()
      }
      audio.play().catch(() => {
        this.$modal.msgError(this.dtT("voicePlayFail"))
        this.stopVoice()
      })
    }
  }
}
</script>

<style scoped lang="scss">
.vet-online-consult {
  display: flex;
  flex-direction: column;
  height: calc(100vh - 84px - 48px);
  max-height: calc(100vh - 84px - 48px);
  overflow: hidden;
  box-sizing: border-box;
}

.vet-online-consult__toolbar {
  flex-shrink: 0;
  margin-bottom: 12px;
}

.vet-online-consult__main {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.vet-online-consult__main.el-card ::v-deep .el-card__body {
  padding: 0;
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.vet-online-consult__layout {
  display: flex;
  flex: 1;
  min-height: 0;
  overflow: hidden;
}

.vet-online-consult__sessions {
  width: 300px;
  flex-shrink: 0;
  min-height: 0;
  border-right: 1px solid #ebeef5;
  display: flex;
  flex-direction: column;
  background: #fafafa;
}

.vet-online-consult__sessions-head {
  flex-shrink: 0;
  padding: 10px 14px 8px;
  border-bottom: 1px solid #ebeef5;
}

.vet-online-consult__sessions-title {
  font-weight: 600;
  font-size: 15px;
  color: #303133;
}

.vet-online-consult__sessions-sub {
  margin-top: 4px;
  font-size: 12px;
  color: #909399;
  line-height: 1.45;
}

.vet-online-consult__sessions-scroll {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
}

.session-item {
  display: flex;
  align-items: flex-start;
  padding: 10px 12px;
  cursor: pointer;
  border-left: 3px solid transparent;
  transition: background 0.15s;

  &:hover {
    background: #f0f2f5;
  }

  &.is-active {
    background: #ecf5ff;
    border-left-color: #409eff;
  }
}

.session-item__avatar {
  flex-shrink: 0;
  background: #dcdfe6;
}

.session-item__text {
  margin-left: 10px;
  min-width: 0;
  flex: 1;
}

.session-item__row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 8px;
}

.session-item__name {
  font-weight: 600;
  font-size: 13px;
  color: #303133;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.session-item__date {
  flex-shrink: 0;
  font-size: 12px;
  color: #909399;
}

.session-item__preview {
  margin-top: 4px;
  font-size: 12px;
  color: #909399;
  line-height: 1.4;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.vet-online-consult__chat {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
  overflow: hidden;
  background: #fff;
}

.vet-online-consult__chat-head {
  flex-shrink: 0;
  text-align: center;
  padding: 14px 16px;
  font-weight: 600;
  font-size: 16px;
  color: #303133;
  border-bottom: 1px solid #ebeef5;
}

.vet-online-consult__chat-body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 12px 16px;
}

.chat-load-hint {
  text-align: center;
  font-size: 12px;
  color: #909399;
  padding: 6px 0 10px;
}

.chat-date-divider {
  text-align: center;
  font-size: 12px;
  color: #909399;
  margin: 12px 0 8px;
}

.chat-row {
  display: flex;
  align-items: flex-start;
  margin-bottom: 14px;
  gap: 10px;

  &.is-asker {
    justify-content: flex-start;
  }

  &.is-vet {
    justify-content: flex-end;
  }
}

.chat-row__avatar {
  flex-shrink: 0;
}

.chat-row.is-asker .chat-row__avatar {
  background-color: #dcdfe6 !important;
}

.chat-row.is-vet .chat-row__avatar {
  background-color: #409eff !important;
  color: #fff !important;
}

.chat-bubble {
  max-width: 72%;
  padding: 8px 12px;
  border-radius: 10px;
  font-size: 14px;
  line-height: 1.5;

  &.is-asker {
    background: #f4f4f5;
    color: #303133;
  }

  &.is-vet {
    background: #409eff;
    color: #fff;
  }
}

.chat-bubble__text {
  white-space: pre-wrap;
  word-break: break-word;
}

.chat-bubble__time {
  margin-top: 4px;
  font-size: 11px;
  opacity: 0.75;
  text-align: right;
}

.chat-bubble__img {
  max-width: 200px;
  max-height: 160px;
  border-radius: 6px;
}

.chat-bubble__video {
  max-width: 240px;
  max-height: 180px;
  border-radius: 6px;
}

.voice-msg {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 88px;
  cursor: pointer;
  user-select: none;
}

.chat-bubble.is-vet .voice-msg {
  color: #fff;
}

.voice-msg__dur {
  font-size: 14px;
  font-weight: 500;
}

.voice-msg__waves {
  display: flex;
  align-items: flex-end;
  gap: 3px;
  height: 16px;

  i {
    display: block;
    width: 3px;
    border-radius: 2px;
    background: currentColor;
  }

  i:nth-child(1) {
    height: 6px;
  }

  i:nth-child(2) {
    height: 10px;
  }

  i:nth-child(3) {
    height: 14px;
  }
}

.voice-msg.is-playing .voice-msg__waves i {
  animation: vet-voice-bar 0.55s ease-in-out infinite alternate;

  &:nth-child(2) {
    animation-delay: 0.12s;
  }

  &:nth-child(3) {
    animation-delay: 0.24s;
  }
}

@keyframes vet-voice-bar {
  from {
    transform: scaleY(0.4);
  }

  to {
    transform: scaleY(1);
  }
}

.vet-online-consult__composer {
  flex-shrink: 0;
  border-top: 1px solid #ebeef5;
  padding: 10px 14px 14px;
  background: #fafafa;
}

.composer-tools {
  display: flex;
  align-items: center;
  gap: 4px;
  margin-bottom: 6px;
}

.composer-upload {
  display: inline-block;
}

.composer-input-row {
  display: flex;
  align-items: flex-end;
  gap: 10px;
}

.composer-send {
  flex-shrink: 0;
  margin-bottom: 4px;
}
</style>
