<template>
  <div class="workspace-3d-wrap" :class="{ 'workspace-3d--canvas-only': canvasOnly || hideToolbar }">
    <div v-if="!(canvasOnly || hideToolbar)" class="workspace-3d-toolbar">
      <el-tag :type="readOnly ? 'info' : 'success'" effect="dark" size="small">
        {{ readOnly ? '只读' : '可交互' }}
      </el-tag>
      <span class="hint">webVRender · http://{{ resolvedHost }} · scope={{ scope }}</span>
      <div class="spacer" />
      <el-tag v-if="lastPick" type="warning" size="small">
        Pick ({{ lastPick.x.toFixed(0) }}, {{ lastPick.y.toFixed(0) }})
      </el-tag>
      <el-tag v-if="lastSelect.length" type="primary" size="small">
        Selected: {{ lastSelect.length }}
      </el-tag>
      <el-button size="small" :icon="Refresh" @click="reload">重载</el-button>
      <el-button size="small" :icon="FullScreen" @click="enterFullscreen">全屏</el-button>
    </div>
    <div ref="frameWrap" class="workspace-3d-frame">
      <iframe
        v-if="iframeSrc"
        ref="frame"
        :src="iframeSrc"
        class="workspace-3d-iframe"
        allow="fullscreen"
        @load="onReady" />
      <div v-if="!loaded" class="workspace-3d-mask">
        <el-icon class="is-loading" size="32"><Loading /></el-icon>
        <span>{{ loadingHint }}</span>
        <span class="muted">如长时间未加载，请确认 SimpleLite 已以 Web-Enabled 模式启动且 8223 端口可达。</span>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
import { Refresh, FullScreen, Loading } from '@element-plus/icons-vue'
import type { Scope } from '@/types/auth'
import { defaultVrHost } from '@/utils/vrender'

interface PickEvent { x: number; y: number }

const props = withDefaults(defineProps<{
  host?: string
  scope?: Scope
  token?: string
  readOnly?: boolean
  /** 嵌入平台页时传 true：SimpleLite 仅显示底栏（对齐/选择/右击/图层/内容/录制回放） */
  embedUi?: boolean
  /**
   * 地图编辑器纯画布模式：比 embedUi 更裸，连底栏都不创建。
   * 设为 true 时 Vue 端会先调 /declareCanvasOnly，并在 iframe URL 上加 ?ui=canvas-only。
   * 适用于 MapEditorView，所有 UI（顶栏 / 工具栏 / 属性面板）由 Vue 接管。
   */
  canvasOnly?: boolean
  /** 隐藏 Vue 端的顶部信息工具条（迷榖 / 重载 / 全屏）。地图编辑器内推荐设为 true，由 EditTopBar 接管。 */
  hideToolbar?: boolean
}>(), {
  host: undefined,
  scope: 'Platform',
  token: '',
  readOnly: false,
  embedUi: false,
  canvasOnly: false,
  hideToolbar: false
})

const emit = defineEmits<{
  (e: 'pick', wp: PickEvent): void
  (e: 'select', names: string[]): void
  (e: 'ready'): void
}>()

const frame = ref<HTMLIFrameElement>()
const frameWrap = ref<HTMLDivElement>()
const loaded = ref(false)
const loadingHint = ref('正在连接 webVRender ...')
const lastPick = ref<PickEvent | null>(null)
const lastSelect = ref<string[]>([])
const iframeSrc = ref<string>('')

const resolvedHost = computed(() => props.host ?? defaultVrHost())

const vrUrl = computed(() => {
  const qs = new URLSearchParams()
  qs.set('scope', props.scope ?? 'Platform')
  if (props.token) qs.set('token', props.token)
  qs.set('ro', props.readOnly ? '1' : '0')
  if (props.canvasOnly) qs.set('ui', 'canvas-only')
  else if (props.embedUi) qs.set('ui', 'embed')
  return `http://${resolvedHost.value}/?${qs.toString()}`
})

const FETCH_TIMEOUT_MS = 1800
const DECLARE_MAX_ATTEMPTS = 4
const LOAD_WATCHDOG_MS = 18_000

let loadWatchdog: ReturnType<typeof setTimeout> | null = null
let prepareGeneration = 0

function vrenderBaseUrl() {
  return `http://${resolvedHost.value}`
}

function sleep(ms: number) {
  return new Promise<void>(r => setTimeout(r, ms))
}

function needsDeclare() {
  return props.canvasOnly || props.embedUi
}

function declareEndpoint() {
  if (props.canvasOnly) return '/declareCanvasOnly'
  if (props.embedUi) return '/declareEmbedUi'
  return null
}

/** 单次 HTTP 探测（带超时），用于 declare / vrenderReady。 */
async function pingVrender(path: string): Promise<boolean> {
  try {
    const res = await fetch(`${vrenderBaseUrl()}${path}`, {
      method: 'GET',
      cache: 'no-store',
      credentials: 'omit',
      signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
    })
    return res.ok
  } catch {
    return false
  }
}

/**
 * 尽力 declare（不阻塞 iframe）。SimpleLite 侧 WebTerminal 已默认 canvas-only，
 * declare 主要用于多 tab 显式标记；失败也不影响平台纯画布模式。
 */
async function postDeclareBestEffort() {
  const endpoint = declareEndpoint()
  if (!endpoint) return

  for (let attempt = 0; attempt < DECLARE_MAX_ATTEMPTS; attempt++) {
    if (await pingVrender(endpoint)) return
    await sleep(80 * (attempt + 1))
  }
  console.warn(`[Workspace3D] ${endpoint} 未成功，将依赖 SimpleLite 默认 canvas-only 模式`)
}

/** 非嵌入模式：短轮询等待 8223 就绪后再加载 iframe。 */
async function waitForVrenderReady(maxMs = 10_000): Promise<boolean> {
  const deadline = Date.now() + maxMs
  let interval = 60
  while (Date.now() < deadline) {
    if (await pingVrender('/vrenderReady')) return true
    await sleep(interval)
    interval = Math.min(interval + 40, 280)
  }
  return false
}

function clearLoadWatchdog() {
  if (loadWatchdog != null) {
    clearTimeout(loadWatchdog)
    loadWatchdog = null
  }
}

function startLoadWatchdog(generation: number) {
  clearLoadWatchdog()
  loadWatchdog = setTimeout(() => {
    if (generation !== prepareGeneration || loaded.value) return
    loadingHint.value = `连接超时 (http://${resolvedHost.value})，请确认 SimpleLite 已启动`
  }, LOAD_WATCHDOG_MS)
}

function buildIframeSrc(forceReload: boolean) {
  const base = vrUrl.value
  if (!forceReload) return base
  const sep = base.includes('?') ? '&' : '?'
  return `${base}${sep}_=${Date.now()}`
}

async function prepareAndLoad(forceReload = false) {
  const generation = ++prepareGeneration
  loaded.value = false
  loadingHint.value = `正在连接 webVRender (http://${resolvedHost.value}) ...`
  startLoadWatchdog(generation)

  const embedded = needsDeclare()

  if (embedded) {
    // 并行：iframe 立即加载，declare 后台尽力发送（不挡首屏）。
    void postDeclareBestEffort()
    iframeSrc.value = buildIframeSrc(forceReload || !!iframeSrc.value)
  } else {
    loadingHint.value = '正在等待 SimpleLite webVRender 就绪 ...'
    await waitForVrenderReady()
    if (generation !== prepareGeneration) return
    iframeSrc.value = buildIframeSrc(forceReload || !!iframeSrc.value)
  }
}

function onReady() {
  loaded.value = true
  clearLoadWatchdog()
  emit('ready')
}

function onMessage(ev: MessageEvent) {
  if (!frame.value || ev.source !== frame.value.contentWindow) return
  const msg = ev.data
  if (!msg || typeof msg !== 'object') return
  if (msg.type === 'workspace.pick' && msg.payload && typeof msg.payload.x === 'number') {
    lastPick.value = msg.payload
    emit('pick', msg.payload)
  } else if (msg.type === 'workspace.select' && Array.isArray(msg.payload)) {
    lastSelect.value = msg.payload as string[]
    emit('select', msg.payload as string[])
  } else if (msg.type === 'workspace.shortcut' && msg.payload && typeof msg.payload === 'object') {
    const action = (msg.payload as { action?: string }).action
    if (action === 'undo' || action === 'redo' || action === 'delete') {
      window.dispatchEvent(new CustomEvent('workspace-shortcut', { detail: { action } }))
    }
  }
}

function reload() {
  prepareAndLoad(true)
}

function enterFullscreen() {
  const el = frameWrap.value
  if (!el) return
  if (document.fullscreenElement) document.exitFullscreen()
  else el.requestFullscreen()
}

onMounted(() => {
  window.addEventListener('message', onMessage)
  prepareAndLoad(false)
})

onBeforeUnmount(() => {
  window.removeEventListener('message', onMessage)
  clearLoadWatchdog()
})

defineExpose({ reload, enterFullscreen })
</script>

<style scoped>
.workspace-3d-wrap {
  display: flex; flex-direction: column; height: 100%; min-height: 480px;
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: var(--mg-radius, 16px);
  background: rgba(15, 4, 32, 0.45);
  backdrop-filter: blur(18px) saturate(140%);
  -webkit-backdrop-filter: blur(18px) saturate(140%);
  box-shadow: 0 8px 24px rgba(10, 2, 16, 0.35), 0 0 0 1px rgba(255, 255, 255, 0.06) inset;
  overflow: hidden;
  transition: all .25s cubic-bezier(.25, .8, .25, 1);
}
.workspace-3d-wrap.workspace-3d--canvas-only {
  border: 0;
  border-radius: 0;
  min-height: 0;
  background: transparent;
  box-shadow: none;
}
.workspace-3d-toolbar {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 14px;
  background: rgba(255, 255, 255, 0.04);
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
  font-size: 12px;
  color: rgba(232, 215, 245, 0.85);
}
.workspace-3d-toolbar .hint {
  color: rgba(201, 166, 227, 0.7);
  font-family: ui-monospace, Menlo, Consolas, monospace;
  font-size: 11.5px;
}
.workspace-3d-toolbar .spacer { flex: 1; }
.workspace-3d-frame {
  flex: 1; position: relative;
  background: radial-gradient(ellipse at 50% 40%, #2d1456 0%, #1a0930 50%, #0f0420 100%);
}
.workspace-3d-iframe {
  width: 100%; height: 100%; border: 0; background: transparent; display: block;
}
.workspace-3d-mask {
  position: absolute; inset: 0; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 10px;
  /* 画布加载光晕固定深紫（不跟浅色壳白底 token，避免被冲成灰白） */
  background:
    radial-gradient(
      ellipse at 50% 42%,
      rgba(155, 124, 255, 0.42) 0%,
      rgba(88, 48, 168, 0.72) 38%,
      rgba(45, 27, 105, 0.92) 68%,
      rgba(15, 4, 32, 0.98) 100%
    );
  color: rgba(232, 220, 255, 0.92);
  font-size: 13px;
  backdrop-filter: blur(10px);
  pointer-events: none;
}
.workspace-3d-mask .muted { color: rgba(196, 181, 253, 0.78); font-size: 12px; }
.workspace-3d-mask .el-icon {
  color: #c4b5fd;
  filter: drop-shadow(0 0 12px rgba(155, 124, 255, 0.65));
}
</style>
