<template>
  <div>
    <v-avatar
      ref="box"
      class="box point elevation-5"
      color="indigo"
      @click="handleClick"
      @mousedown="handleMousedown"
    >
      <v-icon dark large>
        mdi-face-agent
      </v-icon>
    </v-avatar>
    <v-navigation-drawer
      v-model="show"
      fixed
      temporary
      hide-overlay
      right
      width="35%"
      style="z-index: var(--zIndex-nav);"
    >
      <div v-if="show" class="assistant-chat-wrapper d-flex flex-column">
        <v-banner class="flex-shrink-0">
          <div class="my-2">
            <div class="assistant-title">{{ displayTitle }}</div>
            <div v-if="displaySubtitle" class="assistant-subtitle mt-1">{{ displaySubtitle }}</div>
          </div>
        </v-banner>
        <div ref="chatBox" class="chat-list pa-3 flex-grow-1 overflow-auto">
          <v-list three-line>
            <!-- 欢迎语 -->
            <v-list-item>
              <v-list-item-content>
                <v-list-item-title class="d-flex align-center indigo--text">
                  <v-icon class="mr-3" color="indigo">mdi-face-agent</v-icon>
                  {{ system.name }}
                </v-list-item-title>
                <v-list-item-subtitle class="d-flex">
                  <div class="indigo lighten-5 pa-3 round welcome-text">
                    {{ system.welcome }}
                  </div>
                </v-list-item-subtitle>
              </v-list-item-content>
            </v-list-item>
            <v-list-item v-for="talk in talks" :key="talk.id">
              <v-list-item-content>
                <v-list-item-title
                  class="d-flex align-center"
                  :class="talk.type === 'question' ? 'justify-end blue--text' : 'indigo--text'"
                >
                  {{ talk.type === 'question' ? $store.getters.userInfo?.username || '我' : '' }}
                  <v-icon
                    :class="talk.type === 'question' ? 'ml-3' : 'mr-3'"
                    :color="talk.type === 'question' ? 'blue' : 'indigo'"
                  >
                    {{ talk.type === 'question' ? 'mdi-account-circle' : 'mdi-face-agent' }}
                  </v-icon>
                  {{ talk.type === 'question' ? '' : system.name }}
                </v-list-item-title>
                <v-list-item-subtitle class="d-flex" :class="{ 'justify-end': talk.type === 'question' }">
                  <div class="lighten-5 pa-3 round" :class="talk.type === 'question' ? 'blue' : 'indigo'">
                    <div
                      v-for="(line, index) in talk.content"
                      :key="index"
                      class="talk-line"
                      v-html="formatLine(line, talk.type)"
                    ></div>
                  </div>
                </v-list-item-subtitle>
              </v-list-item-content>
            </v-list-item>
            <!-- loading -->
            <v-list-item v-show="loading" style="min-height: 10px">
              <v-list-item-content>
                <v-list-item-title class="d-flex align-center indigo--text">
                  <v-icon class="mr-3" color="indigo">mdi-face-agent</v-icon>
                  <div class="mr-3">{{ system.name }} 思考中</div>
                  <v-progress-circular size="18" width="2" indeterminate color="indigo" />
                </v-list-item-title>
              </v-list-item-content>
            </v-list-item>
          </v-list>
        </div>
        <div class="send d-flex align-center justify-center flex-shrink-0">
          <div class="send-box">
            <v-textarea
              v-model="question"
              dense
              class="send-box-area"
              auto-grow
              :placeholder="displayInputPlaceholder"
              solo
              hide-details
              no-resize
              rows="1"
              @keydown.enter="handleKeyCode"
            />
            <v-btn icon color="primary" class="btn" :disabled="!question.trim()" @click="handleSendMsg">
              <v-icon>mdi-send</v-icon>
            </v-btn>
          </div>
        </div>
      </div>
    </v-navigation-drawer>
  </div>
</template>

<script>
import { generateUUID } from '@/utils/index'

const DEFAULT_WEBHOOK_URL = process.env.VUE_APP_N8N_CHAT_URL || ''

export default {
  name: 'AssistantIframe',
  props: {
    /** n8n Chat Trigger 的 webhook 地址，用于发消息拿回复 */
    iframeUrl: {
      type: String,
      default: DEFAULT_WEBHOOK_URL
    },
    /** 抽屉标题 */
    title: {
      type: String,
      default: '智能助手'
    },
    /** 欢迎语 */
    welcome: {
      type: String,
      default: '您好，我是您的智能助手，有什么问题可以问我哦！'
    },
    inputPlaceholder: {
      type: String,
      default: '请输入您想问的内容，按 Ctrl+Enter 换行'
    },
    subTitle: {
      type: String,
      default: ''
    }
  },
  data () {
    return {
      show: false,
      lock: false,
      isDragging: false,
      info: {},
      sessionId: '',
      displayTitle: this.title,
      displaySubtitle: this.subTitle,
      displayInputPlaceholder: this.inputPlaceholder,
      system: {
        name: '智能助手',
        welcome: this.welcome
      },
      talks: [],
      question: '',
      loading: false,
      talkId: 0,
      configFetched: false
    }
  },
  watch: {
    show (val) {
      if (val && !this.sessionId) {
        this.sessionId = generateUUID()
      }
      if (val && this.iframeUrl && !this.configFetched) {
        this.fetchConfig()
      }
    },
    title (val) {
      if (!this.configFetched) this.displayTitle = val
    },
    welcome (val) {
      if (!this.configFetched) this.system.welcome = val
    },
    talks: {
      handler () {
        this.$nextTick(() => {
          const box = this.$refs.chatBox
          if (box) {
            box.scrollTo({ top: box.scrollHeight, behavior: 'smooth' })
          }
        })
      },
      deep: true,
      immediate: true
    }
  },
  mounted () {
    this.sessionId = generateUUID()
    this.$nextTick(() => {
      document.addEventListener('mouseup', this.handleMouseup)
      document.addEventListener('mousemove', this.handleMousemove)
      this.$once('hook:beforeDestroy', () => {
        document.removeEventListener('mouseup', this.handleMouseup)
        document.removeEventListener('mousemove', this.handleMousemove)
      })
    })
  },
  methods: {
    handleClick () {
      if (this.isDragging) return
      this.show = !this.show
    },
    handleMousedown (e) {
      this.lock = true
      const { left, top, height, width } = this.$refs.box.$el.getBoundingClientRect()
      this.info = {
        click: { x: e.clientX, y: e.clientY },
        left,
        top,
        height,
        width,
        clientX: e.clientX,
        clientY: e.clientY
      }
    },
    handleMousemove (e) {
      if (!this.lock) return
      const xLen = Math.abs(this.info.click?.x - e.clientX)
      const yLen = Math.abs(this.info.click?.y - e.clientY)
      if (xLen < 20 && yLen < 20) return
      const clientX = e.clientX - this.info.clientX
      const clientY = e.clientY - this.info.clientY
      this.isDragging = true
      this.info.left += clientX
      this.info.top += clientY
      this.$refs.box.$el.style.left = this.info.left + 'px'
      this.$refs.box.$el.style.top = this.info.top + 'px'
      this.info.clientX = e.clientX
      this.info.clientY = e.clientY
    },
    handleMouseup () {
      this.lock = false
      setTimeout(() => { this.isDragging = false })
    },
    /** 从 n8n chat 页面的 HTML 中解析 createChat 的 i18n.en / initialMessages */
    parseConfigFromHtml (raw) {
      const config = {}
      const str = raw.replace(/\s+/g, ' ')
      const titleMatch = str.match(/"title"\s*:\s*"((?:[^"\\]|\\.)*)"/)
      if (titleMatch) config.title = titleMatch[1].replace(/\\"/g, '"')
      const subtitleMatch = str.match(/"subtitle"\s*:\s*"((?:[^"\\]|\\.)*)"/)
      if (subtitleMatch) config.subtitle = subtitleMatch[1].replace(/\\"/g, '"')
      const placeholderMatch = str.match(/"inputPlaceholder"\s*:\s*"((?:[^"\\]|\\.)*)"/)
      if (placeholderMatch) config.inputPlaceholder = placeholderMatch[1].replace(/\\"/g, '"')
      const initialMessages = this.parseInitialMessagesArray(raw)
      if (initialMessages.length) {
        config.welcome = initialMessages.join('\n')
      }
      return config
    },
    /** 解析 initialMessages 数组中的全部字符串（支持任意条数） */
    parseInitialMessagesArray (raw) {
      const idx = raw.indexOf('initialMessages')
      if (idx === -1) return []
      const arrStart = raw.indexOf('[', idx)
      if (arrStart === -1) return []
      let depth = 1
      let pos = arrStart + 1
      while (pos < raw.length && depth > 0) {
        const ch = raw[pos]
        if (ch === '[') depth++
        else if (ch === ']') depth--
        pos++
      }
      const arrContent = raw.slice(arrStart + 1, pos - 1)
      const list = []
      const re = /"((?:[^"\\]|\\.)*)"/g
      let m
      while ((m = re.exec(arrContent)) !== null) {
        list.push(m[1].replace(/\\"/g, '"'))
      }
      return list
    },
    async fetchConfig () {
      if (!this.iframeUrl) return
      try {
        const res = await fetch(this.iframeUrl, { method: 'GET' })
        if (!res.ok) return
        const text = await res.text()
        const raw = text && text.trim()
        if (!raw) return
        let config = null
        if (raw.startsWith('{')) {
          const data = JSON.parse(raw)
          config = data && typeof data.data !== 'undefined' ? data.data : data
        } else if (raw.toLowerCase().includes('createchat') && (raw.includes('i18n') || raw.includes('title') || raw.includes('initialMessages'))) {
          config = this.parseConfigFromHtml(raw)
        }
        if (!config || typeof config !== 'object') return
        this.configFetched = true
        if (config.title != null) this.displayTitle = String(config.title)
        if (config.subtitle != null) this.displaySubtitle = String(config.subtitle)
        const placeholder = config.inputPlaceholder ?? config.placeholder
        if (placeholder != null) this.displayInputPlaceholder = String(placeholder)
        if (config.welcome != null) this.system.welcome = String(config.welcome)
      } catch (_) {
        // GET 返回 HTML 或非 JSON 时已在上方按 createChat 解析，失败则保留 props 默认值
      }
    },
    escapeHtml (text) {
      if (text === null || text === undefined) return ''
      return String(text)
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
    },
    formatLine (line, type) {
      if (type !== 'response') {
        return this.escapeHtml(line)
      }
      return this.formatResponseLine(line)
    },
    formatResponseLine (line) {
      const raw = typeof line === 'string' ? line : String(line || '')
      const trimmed = raw.replace(/\r?\n$/, '')
      if (!trimmed) {
        return '&nbsp;'
      }

      // Markdown 标题：# / ## / ### ... 开头，# 数量代表 h 级别
      const headingMatch = trimmed.match(/^\s*(#{1,6})\s+(.+)$/)
      if (headingMatch) {
        const level = headingMatch[1].length
        const text = headingMatch[2]
        const safe = this.escapeHtml(text)
        return `<strong class="talk-heading-${level}">${safe}</strong>`
      }

      let isBullet = false
      let work = trimmed

      // 列表项：- 开头
      if (/^\s*-\s+/.test(work)) {
        isBullet = true
        work = work.replace(/^\s*-\s+/, '')
      }

      let html = this.escapeHtml(work)

      // 加粗：**text**
      html = html.replace(/\*\*((?:[^*]|\*)+?)\*\*/g, '<strong>$1</strong>')

      if (isBullet) {
        return `<span class="talk-bullet-dot">• </span>${html}`
      }

      return html
    },
    handleKeyCode (event) {
      if (event.keyCode === 13) {
        if (!event.ctrlKey) {
          event.preventDefault()
          this.handleSendMsg()
        } else {
          this.question += '\n'
        }
      }
    },
    async handleSendMsg () {
      const text = this.question.trim()
      if (!text) return

      this.talks.push({
        id: ++this.talkId,
        type: 'question',
        content: text.split('\n')
      })
      this.question = ''
      this.loading = true

      try {
        const response = await fetch(this.iframeUrl, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            chatInput: text,
            sessionId: this.sessionId
          })
        })
        if (!response.ok) {
          throw new Error(response.statusText || '请求失败')
        }
        const data = await response.json()
        const output = data.output ?? data.text ?? (typeof data === 'string' ? data : '')
        const content = (output && String(output).trim()) ? String(output).split('\n') : ['暂无回复']
        this.talks.push({
          id: ++this.talkId,
          type: 'response',
          content
        })
      } catch (err) {
        const msg = err?.message || err || '网络或服务异常'
        this.$snackbar?.error ? this.$snackbar.error(msg) : this.$message?.({ type: 'error', message: msg })
        this.talks.push({
          id: ++this.talkId,
          type: 'response',
          content: [`请求失败：${msg}`]
        })
      } finally {
        this.loading = false
      }
    }
  }
}
</script>

<style lang="scss" scoped>
.point {
  cursor: pointer;
}
.box {
  z-index: 9;
  position: fixed;
  right: 50px;
  bottom: 100px;
}
.assistant-chat-wrapper {
  height: 100%;
}
.assistant-title {
  font-size: 1.25rem;
  font-weight: 600;
}
.assistant-subtitle {
  font-size: 0.875rem;
  opacity: 0.9;
}
.chat-list {
  min-height: 0;
}
.round {
  border-radius: 5px;
  max-width: 80%;
}
.welcome-text {
  white-space: pre-line;
}
.talk-line {
  white-space: pre-wrap;
  word-break: break-word;
}
.talk-heading-1,
.talk-heading-2,
.talk-heading-3,
.talk-heading-4,
.talk-heading-5,
.talk-heading-6 {
  font-weight: 600;
}
.talk-heading-1 { font-size: 1.2rem; }
.talk-heading-2 { font-size: 1.1rem; }
.talk-heading-3 { font-size: 1.05rem; }
.talk-heading-4 { font-size: 1rem; }
.talk-heading-5 { font-size: 0.95rem; }
.talk-heading-6 { font-size: 0.9rem; }
.talk-bullet-dot {
  margin-right: 4px;
}
.send {
  padding: 20px;
  .send-box {
    width: 100%;
    position: relative;
    .btn {
      position: absolute;
      right: 20px;
      bottom: 12px;
    }
    .send-box-area {
      ::v-deep textarea {
        padding: 15px 70px 15px 0 !important;
        max-height: 300px;
        min-height: 60px;
        overflow: auto;
        margin: 0 !important;
      }
    }
  }
}
</style>
