<template>
  <div
    class="ai-assistant-panel"
    :class="{ 'is-open': open }"
    :style="{ width: panelWidth + 'px' }"
    role="complementary"
    aria-label="AI 助手"
  >
    <div
      class="aap-resizer"
      title="拖拽调整宽度"
      @pointerdown="onResizeStart"
      @pointermove="onResizeMove"
      @pointerup="onResizeEnd"
      @pointercancel="onResizeEnd"
    ></div>

    <div class="aap-header">
      <div class="aap-title">
        <span class="aap-glyph">✦</span>
        <div class="aap-title-text">
          <div class="aap-title-main">AI 助手</div>
          <div class="aap-title-sub">用自然语言描述地图需求，自动生成站点 / 路径</div>
        </div>
      </div>
      <button class="aap-close" type="button" title="收起" @click="close">✕</button>
    </div>

    <el-alert v-if="!configured" type="warning" :closable="false" class="aap-alert">
      尚未配置 AI 服务（apiKey / endpoint）。请先到
      <el-link type="primary" @click="goConfig">系统级配置 → AI 服务</el-link>
      完成配置。
    </el-alert>

    <div ref="listRef" class="aap-messages">
      <div v-if="messages.length === 0" class="aap-empty">
        <div class="aap-empty-title">试着这样说：</div>
        <button
          v-for="(ex, i) in examples"
          :key="i"
          type="button"
          class="aap-example"
          @click="useExample(ex)"
        >{{ ex }}</button>
      </div>

      <div
        v-for="(m, i) in messages"
        :key="i"
        class="aap-msg"
        :class="`aap-msg--${m.role}`"
      >
        <div class="aap-bubble">
          <div v-if="m.role === 'user'" class="aap-bubble-text">{{ m.text }}</div>
          <div v-else class="aap-md" v-html="renderMd(m.text)"></div>
          <div v-if="m.role === 'assistant' && m.meta" class="aap-meta">
            <el-icon class="aap-meta-ic"><CircleCheck /></el-icon>
            已落地 <b>{{ m.meta.created }}</b> 个对象 · 工具调用 <b>{{ m.meta.usedTools }}</b>
          </div>
        </div>
      </div>

      <div v-if="busy" class="aap-msg aap-msg--assistant">
        <div class="aap-bubble aap-bubble--loading">
          <el-icon class="is-loading"><Loading /></el-icon>
          <span>AI 正在生成…</span>
        </div>
      </div>
    </div>

    <div class="aap-toolbar">
      <span class="aap-toolbar-label">生成模式</span>
      <el-radio-group v-model="mode" size="small">
        <el-radio-button value="sites">站点</el-radio-button>
        <el-radio-button value="tracks">路径</el-radio-button>
        <el-radio-button value="sites+tracks">站点+路径</el-radio-button>
        <el-radio-button value="full">完整</el-radio-button>
      </el-radio-group>
    </div>

    <div class="aap-input">
      <el-input
        v-model="draft"
        type="textarea"
        :rows="3"
        resize="none"
        :disabled="!configured || busy"
        placeholder="例如：一条 U 型生产线，含 5 个工站，间距 2m，单向通行…（Enter 发送，Shift+Enter 换行）"
        @keydown="onKeydown"
      />
      <el-button
        type="primary"
        class="aap-send"
        :loading="busy"
        :disabled="!configured || !draft.trim()"
        @click="send"
      >发送</el-button>
    </div>
  </div>
</template>

<script setup lang="ts">
import { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue'
import { Loading, CircleCheck } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import { useRouter } from 'vue-router'
import { mapEditApi, type AiMapGenerateRequest, type AiMapGenerateResult } from '@/api/mapEdit'
import { renderMarkdown } from '@/utils/miniMarkdown'

const props = defineProps<{
  /** 面板是否展开（停靠在右侧）。 */
  open: boolean
  /** AI 服务是否已配置 apiKey / endpoint。 */
  configured: boolean
  /** 面板宽度（px），由父级持久化；可拖拽左沿调整。 */
  width?: number
  /** 生成范围默认值 x1,y1,x2,y2（mm），沿用编辑器默认。 */
  defaultBounds?: [number, number, number, number]
  /** 生成对象默认落点图层。 */
  defaultLayer?: string
}>()

const emit = defineEmits<{
  (e: 'update:open', v: boolean): void
  (e: 'update:width', v: number): void
  (e: 'generated', r: AiMapGenerateResult): void
}>()

/** 宽度约束：保证内容（按钮 / 单选组）不被压垮，也不至于把画布挤没。 */
const MIN_WIDTH = 300
const MAX_WIDTH = 720
const panelWidth = computed(() => Math.min(MAX_WIDTH, Math.max(MIN_WIDTH, props.width ?? 360)))

const router = useRouter()

interface ChatMessage {
  role: 'user' | 'assistant'
  text: string
  meta?: { created: number; usedTools: number }
}

const messages = ref<ChatMessage[]>([])
const draft = ref('')
const busy = ref(false)
const listRef = ref<HTMLElement | null>(null)

// 记忆上次使用的生成模式：下次打开沿用，免去每次重选。
type GenMode = NonNullable<AiMapGenerateRequest['mode']>
const MODE_KEY = 'mapEditor.aiAssistant.mode'
function loadMode(): GenMode {
  const v = localStorage.getItem(MODE_KEY)
  if (v === 'sites' || v === 'tracks' || v === 'sites+tracks' || v === 'full') return v
  return 'sites+tracks'
}
const mode = ref<GenMode>(loadMode())
watch(mode, (v) => {
  try { localStorage.setItem(MODE_KEY, v) } catch { /* localStorage 不可用则忽略 */ }
})

// ── 拖拽调整宽度 ──
// 面板停靠右侧，左沿手柄向左拖 → 变宽。用 setPointerCapture 把后续 pointermove 锁定到
// 手柄元素上，避免指针移到中间的 webVRender iframe 上方时事件被 iframe 吞掉、拖拽中断。
let resizing = false
let resizeStartX = 0
let resizeStartW = 0
function onResizeStart(e: PointerEvent) {
  resizing = true
  resizeStartX = e.clientX
  resizeStartW = panelWidth.value
  ;(e.currentTarget as HTMLElement).setPointerCapture?.(e.pointerId)
  e.preventDefault()
}
function onResizeMove(e: PointerEvent) {
  if (!resizing) return
  const next = Math.min(MAX_WIDTH, Math.max(MIN_WIDTH, resizeStartW + (resizeStartX - e.clientX)))
  emit('update:width', next)
}
function onResizeEnd(e: PointerEvent) {
  if (!resizing) return
  resizing = false
  ;(e.currentTarget as HTMLElement).releasePointerCapture?.(e.pointerId)
}
onBeforeUnmount(() => { resizing = false })

const examples = [
  '生成一条横向直线，5 个站点，间距 2000mm',
  '画一个 3×3 的站点矩阵，间距 2500mm',
  '一条 U 型产线，含 5 个工站，单向通行'
]

function close() {
  emit('update:open', false)
}

function goConfig() {
  close()
  router.push('/admin/config/system')
}

function useExample(ex: string) {
  draft.value = ex
}

function renderMd(s: string): string {
  return renderMarkdown(s)
}

function onKeydown(e: Event | KeyboardEvent) {
  // el-input 的 keydown 事件签名是 Event | KeyboardEvent，这里收窄到键盘事件。
  // Enter 发送、Shift+Enter 换行；中文输入法组合期间（isComposing）不触发发送。
  if (!(e instanceof KeyboardEvent)) return
  if (e.key === 'Enter' && !e.shiftKey && !e.isComposing) {
    e.preventDefault()
    void send()
  }
}

async function scrollToBottom() {
  await nextTick()
  const el = listRef.value
  if (el) el.scrollTop = el.scrollHeight
}

async function send() {
  const text = draft.value.trim()
  if (!text || busy.value || !props.configured) return

  messages.value.push({ role: 'user', text })
  draft.value = ''
  void scrollToBottom()

  busy.value = true
  try {
    const req: AiMapGenerateRequest = {
      prompt: text,
      mode: mode.value,
      bounds: props.defaultBounds,
      layer: props.defaultLayer
    }
    const r = await mapEditApi.aiMapGenerate(req)
    const created = r.created?.length ?? 0
    messages.value.push({
      role: 'assistant',
      text: r.assistantText?.trim() || `已根据你的描述生成并落地 ${created} 个对象。`,
      meta: { created, usedTools: r.usedTools ?? 0 }
    })
    emit('generated', r)
  } catch (err) {
    const msg = (err as Error).message
    messages.value.push({ role: 'assistant', text: `生成失败：${msg}` })
    ElMessage.error(`AI 助手生成失败：${msg}`)
  } finally {
    busy.value = false
    void scrollToBottom()
  }
}
</script>

<style scoped>
.ai-assistant-panel {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  z-index: 30;
  display: flex;
  flex-direction: column;
  /* 实底（高不透明），解决「太透明看不清」 */
  background: linear-gradient(180deg, rgba(28, 12, 56, 0.98) 0%, rgba(16, 6, 34, 0.99) 100%);
  border-left: 1px solid rgba(190, 140, 240, 0.28);
  box-shadow: -10px 0 30px rgba(8, 2, 16, 0.55);
  color: rgba(236, 224, 250, 0.95);
  transform: translateX(100%);
  opacity: 0;
  visibility: hidden;
  transition: transform 0.26s cubic-bezier(0.25, 0.8, 0.25, 1), opacity 0.26s ease, visibility 0.26s;
}
.ai-assistant-panel.is-open {
  transform: translateX(0);
  opacity: 1;
  visibility: visible;
}

/* 左沿拖拽手柄：覆盖在 border-left 上方，hover 高亮提示可拖拽。 */
.aap-resizer {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 6px;
  cursor: ew-resize;
  z-index: 5;
  background: transparent;
  transition: background 0.15s ease;
  touch-action: none;
}
.aap-resizer:hover { background: rgba(190, 140, 240, 0.45); }

.aap-header {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 12px 14px;
  background: linear-gradient(135deg, rgba(120, 70, 220, 0.5) 0%, rgba(255, 90, 200, 0.4) 100%);
  border-bottom: 1px solid rgba(255, 255, 255, 0.12);
}
.aap-title { display: flex; align-items: center; gap: 10px; min-width: 0; }
.aap-glyph {
  font-size: 20px;
  color: #fff;
  text-shadow: 0 0 10px rgba(255, 200, 250, 0.7);
  flex: none;
}
.aap-title-text { min-width: 0; }
.aap-title-main { font-size: 15px; font-weight: 700; color: #fff; line-height: 1.2; }
.aap-title-sub { font-size: 11px; color: rgba(240, 222, 255, 0.78); margin-top: 2px; }
.aap-close {
  appearance: none;
  border: 0;
  background: rgba(255, 255, 255, 0.12);
  color: #fff;
  width: 26px;
  height: 26px;
  border-radius: 6px;
  cursor: pointer;
  font-size: 13px;
  line-height: 1;
  flex: none;
  transition: background 0.15s ease;
}
.aap-close:hover { background: rgba(255, 255, 255, 0.24); }

.aap-alert { margin: 10px 12px 0; }

.aap-messages {
  flex: 1 1 0;
  min-height: 0;
  overflow-y: auto;
  padding: 12px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.aap-empty { padding: 8px 2px; display: flex; flex-direction: column; gap: 8px; }
.aap-empty-title { font-size: 12px; color: rgba(210, 188, 240, 0.7); }
.aap-example {
  appearance: none;
  text-align: left;
  border: 1px dashed rgba(190, 140, 240, 0.4);
  background: rgba(255, 255, 255, 0.04);
  color: rgba(232, 215, 245, 0.9);
  border-radius: 8px;
  padding: 8px 10px;
  font-size: 12.5px;
  cursor: pointer;
  transition: all 0.15s ease;
}
.aap-example:hover {
  background: rgba(150, 90, 230, 0.22);
  border-color: rgba(190, 140, 240, 0.7);
  color: #fff;
}

.aap-msg { display: flex; }
.aap-msg--user { justify-content: flex-end; }
.aap-msg--assistant { justify-content: flex-start; }
.aap-bubble {
  max-width: 86%;
  padding: 8px 11px;
  border-radius: 12px;
  font-size: 13px;
  line-height: 1.5;
}
.aap-msg--user .aap-bubble {
  background: linear-gradient(135deg, rgba(150, 90, 240, 0.95) 0%, rgba(120, 70, 220, 0.95) 100%);
  color: #fff;
  border-bottom-right-radius: 4px;
}
.aap-msg--assistant .aap-bubble {
  background: rgba(255, 255, 255, 0.07);
  border: 1px solid rgba(255, 255, 255, 0.1);
  color: rgba(236, 224, 250, 0.95);
  border-bottom-left-radius: 4px;
}
.aap-bubble-text { white-space: pre-wrap; word-break: break-word; }
.aap-meta {
  margin-top: 6px;
  padding-top: 6px;
  border-top: 1px dashed rgba(255, 255, 255, 0.14);
  font-size: 11.5px;
  color: rgba(210, 188, 240, 0.8);
}
.aap-meta b { color: var(--mg-accent, #c4a4ff); }
.aap-meta-ic { color: #6ee7b7; margin-right: 4px; vertical-align: -2px; }

/* assistant 消息 Markdown 渲染 */
.aap-md { font-size: 13px; line-height: 1.6; word-break: break-word; color: rgba(236, 224, 250, 0.95); }
.aap-md :deep(p) { margin: 0 0 7px; }
.aap-md :deep(p:last-child) { margin-bottom: 0; }
.aap-md :deep(.mmd-h) { margin: 8px 0 5px; font-weight: 700; color: #fff; }
.aap-md :deep(h3.mmd-h) { font-size: 14px; }
.aap-md :deep(h4.mmd-h) { font-size: 13px; }
.aap-md :deep(.mmd-ul), .aap-md :deep(.mmd-ol) { margin: 4px 0 7px; padding-left: 18px; }
.aap-md :deep(li) { margin: 2px 0; }
.aap-md :deep(.mmd-code) {
  font-family: var(--mg-font-mono, monospace);
  font-size: 12px;
  background: rgba(0, 0, 0, 0.4);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 5px;
  padding: 1px 5px;
}
.aap-md :deep(.mmd-pre) {
  margin: 6px 0;
  padding: 9px 11px;
  background: rgba(0, 0, 0, 0.42);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 8px;
  overflow: auto;
  max-height: 280px;
}
.aap-md :deep(.mmd-pre code) {
  font-family: var(--mg-font-mono, monospace);
  font-size: 12px;
  color: rgba(220, 230, 245, 0.95);
  white-space: pre;
}
.aap-md :deep(.mmd-link) { color: var(--mg-accent, #c4a4ff); text-decoration: underline; }
.aap-md :deep(.mmd-quote) {
  margin: 6px 0;
  padding: 3px 10px;
  border-left: 3px solid rgba(190, 140, 240, 0.6);
  color: rgba(220, 205, 250, 0.85);
}
.aap-md :deep(strong) { color: #fff; }
.aap-bubble--loading {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  color: rgba(210, 188, 240, 0.85);
}

.aap-toolbar {
  flex: none;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 12px 0;
  flex-wrap: wrap;
}
.aap-toolbar-label { font-size: 11.5px; color: rgba(210, 188, 240, 0.7); flex: none; }
.aap-toolbar :deep(.el-radio-button__inner) {
  padding: 5px 9px;
  font-size: 12px;
}

.aap-input {
  flex: none;
  padding: 8px 12px 12px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.aap-send { align-self: flex-end; min-width: 84px; }
</style>
