<template>
  <div class="monitor-selection">
    <template v-if="!selection">
      <div class="empty-hint">
        <span class="empty-title">未选中对象</span>
        <span class="empty-desc">在 3D 画布或车辆列表中点击以查看信息。</span>
      </div>
    </template>

    <div v-else class="pane">
      <div v-if="initialLoading" class="pane-loading">
        <el-icon class="is-loading"><Loading /></el-icon>
        <span>加载中…</span>
      </div>

      <!-- 站点 -->
      <template v-else-if="viewKind === 'site'">
        <section class="card card--header">
          <div class="title-row">
            <span class="id-tag">#{{ objectId }}</span>
            <span class="type-tag">站点</span>
          </div>
          <div class="name-line">{{ siteInfo.name }}</div>
        </section>
        <section class="card">
          <header class="card-hd"><span>位置</span></header>
          <div class="kv-grid">
            <div class="kv"><span class="k">X</span><span class="v mono">{{ siteInfo.x }}</span></div>
            <div class="kv"><span class="k">Y</span><span class="v mono">{{ siteInfo.y }}</span></div>
            <div class="kv"><span class="k">Z</span><span class="v mono">{{ siteInfo.z }}</span></div>
          </div>
        </section>
        <section v-if="statusDisplayRows.length" class="card">
          <header class="card-hd"><span>状态</span><span class="meta">{{ statusDisplayRows.length }}</span></header>
          <div class="kv-list">
            <div v-for="row in statusDisplayRows" :key="row.key" class="kv-row">
              <span class="k">{{ row.key }}</span>
              <span class="v" :title="row.value">{{ row.value }}</span>
            </div>
          </div>
        </section>
        <section v-if="!readOnly && siteActions.length" class="card card--actions">
          <header class="card-hd"><span>操作</span></header>
          <div class="action-grid">
            <button
              v-for="m in siteActions"
              :key="m.methodName"
              type="button"
              class="action-btn"
              :disabled="executing === m.methodName"
              @click="onExecute(m)">
              {{ m.label || m.methodName }}
            </button>
          </div>
        </section>
      </template>

      <!-- 车辆 -->
      <template v-else-if="viewKind === 'vehicle'">
        <section class="card card--header">
          <div class="title-row">
            <span class="id-tag">{{ vehicleSummary.id }}</span>
            <el-tag size="small" :type="vehicleSummary.runTagType" effect="dark">
              {{ vehicleSummary.runLabel }}
            </el-tag>
          </div>
          <div class="name-line">{{ vehicleSummary.name }}</div>
        </section>

        <section class="card">
          <header class="card-hd"><span>运行信息</span></header>
          <div class="kv-list">
            <div class="kv-row">
              <span class="k">交管状态</span>
              <span class="v" :title="vehicleSummary.traffic">{{ vehicleSummary.traffic }}</span>
            </div>
            <div class="kv-row">
              <span class="k">运行状态</span>
              <span class="v">{{ vehicleSummary.runLabel }}</span>
            </div>
            <div class="kv-row" :class="{ 'is-warn': vehicleSummary.alarmWarn }">
              <span class="k">报警状态</span>
              <span class="v" :title="vehicleSummary.alarm">{{ vehicleSummary.alarm }}</span>
            </div>
            <div class="kv-row">
              <span class="k">电量</span>
              <span class="v" :class="{ 'is-low-battery': vehicleSummary.batteryLow }">
                {{ vehicleSummary.battery }}
              </span>
            </div>
            <div class="kv-row">
              <span class="k">位置</span>
              <span class="v mono" :title="vehicleSummary.position">{{ vehicleSummary.position }}</span>
            </div>
          </div>
        </section>

        <section class="card card--actions">
          <header class="card-hd">
            <span>{{ readOnly ? '运维动作' : '动作' }}</span>
            <span v-if="!readOnly && carActions.length" class="meta">{{ carActions.length }}</span>
            <span v-else-if="readOnly && opsCarActions.length" class="meta">{{ opsCarActions.length }}</span>
          </header>
          <template v-if="readOnly">
            <div v-if="!opsCarActions.length" class="muted-line">当前账号无可执行的运维动作。</div>
            <div v-else class="action-grid action-grid--vehicle">
              <button
                v-for="op in opsCarActions"
                :key="op.code"
                type="button"
                class="action-btn"
                :class="{ 'action-btn--goto': op.needConfirm }"
                :disabled="executing === op.code"
                :title="op.description"
                @click="onOpsExecute(op)">
                {{ op.label }}
              </button>
            </div>
          </template>
          <template v-else>
            <div v-if="actionsLoading" class="muted-line">加载动作…</div>
            <div v-else-if="!carActions.length" class="muted-line">{{ carActionHint }}</div>
            <div v-else class="action-grid action-grid--vehicle">
              <button
                v-for="m in carActions"
                :key="m.methodName"
                type="button"
                class="action-btn"
                :class="{ 'action-btn--goto': methodNeedsSitePick(m) }"
                :disabled="executing === m.methodName"
                @click="onExecute(m)">
                {{ m.label || m.methodName }}
              </button>
            </div>
          </template>
        </section>
      </template>

      <!-- 路径 -->
      <template v-else-if="viewKind === 'track'">
        <section class="card card--header">
          <div class="title-row">
            <span class="id-tag">#{{ objectId }}</span>
            <span class="type-tag">路径</span>
          </div>
          <div class="name-line">{{ trackInfo.name }}</div>
        </section>
        <section v-if="statusDisplayRows.length || trackRows.length" class="card">
          <header class="card-hd"><span>信息</span></header>
          <div class="kv-list">
            <div v-for="row in trackRows" :key="row.key" class="kv-row">
              <span class="k">{{ row.label }}</span>
              <span class="v">{{ row.value }}</span>
            </div>
            <div v-for="row in statusDisplayRows" :key="'s-' + row.key" class="kv-row">
              <span class="k">{{ row.key }}</span>
              <span class="v">{{ row.value }}</span>
            </div>
          </div>
        </section>
        <section v-if="!readOnly && trackActions.length" class="card card--actions">
          <header class="card-hd"><span>动作</span></header>
          <div class="action-grid">
            <button
              v-for="m in trackActions"
              :key="m.methodName"
              type="button"
              class="action-btn"
              :disabled="executing === m.methodName"
              @click="onExecute(m)">
              {{ m.label || m.methodName }}
            </button>
          </div>
        </section>
      </template>

      <template v-else>
        <div class="muted-line">该类型暂不支持监控详情展示。</div>
      </template>
    </div>

    <SitePickDialog
      v-model="sitePickOpen"
      :title="sitePickTitle"
      :sites="sitePickSites"
      @confirm="onSitePickConfirm"
      @pick-on-map="onSitePickOnMap"
    />
  </div>
</template>

<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Loading } from '@element-plus/icons-vue'
import SitePickDialog from '@/components/workbench/SitePickDialog.vue'
import {
  reflectionApi,
  type ReflectionKind,
  type ReflectionKv,
  type ReflectionMethod
} from '@/api/reflection'
import {
  executeCarGotoSite,
  executeReflectionMethod,
  loadSitePickOptions,
  methodNeedsSitePick,
  pickTargetSiteOnMap,
  type SitePickOption
} from '@/utils/carActionExecute'
import { OPS_WHITELIST, type OpsAction } from '@/types/ops'
import { executeOp } from '@/api/ops'
import { useAuthStore } from '@/stores/auth'
import { fetchMonitorConfigCached } from '@/utils/monitorConfigCache'
import {
  extractSocFromFieldMap,
  extractSocFromKv,
  socToPercent
} from '@/utils/batterySoc'
import type { Car } from '@/types/car'
import type { Mission } from '@/types/mission'
import type { SelectedObjectRef } from '@/types/workbench'

const SITE_ACTION_METHODS = new Set(['AllowLock', 'PreventLock'])

const props = defineProps<{
  selection: SelectedObjectRef | null
  refreshKey?: number
  cars?: Car[]
  missions?: Mission[]
  /** 运营端只读模式：3D 不可编辑，车辆动作改用运维白名单（executeOp + 审计），并跳过 reflection 动作/配置加载。 */
  readOnly?: boolean
}>()

const auth = useAuthStore()

const initialLoading = ref(false)
const hydrated = ref(false)
const actionsLoading = ref(false)
const executing = ref<string | null>(null)
const pendingGotoMethod = ref<ReflectionMethod | null>(null)

const bundleName = ref('')
const bundleTypeName = ref('')
const bundleFullTypeName = ref('')
const fieldMap = ref<Record<string, string>>({})
const statusRows = ref<ReflectionKv[]>([])
const allMethods = ref<ReflectionMethod[]>([])
const carActionByType = ref<Record<string, string[]>>({})
const siteActionKeys = ref<string[]>([])
const trackActionKeys = ref<string[]>([])
const monitorConfigLoaded = ref(false)

const sitePickOpen = ref(false)
const sitePickSites = ref<SitePickOption[]>([])
const sitePickTitle = ref('选择目标站点')

const SELECTION_TO_REFLECTION: Record<string, ReflectionKind> = {
  car: 'car',
  vehicle: 'car',
  site: 'site',
  track: 'track'
}

const reflectionKind = computed<ReflectionKind | null>(() => {
  const k = props.selection?.kind?.toLowerCase()
  return k ? SELECTION_TO_REFLECTION[k] ?? null : null
})

const viewKind = computed(() => {
  const k = props.selection?.kind?.toLowerCase()
  if (k === 'site') return 'site'
  if (k === 'vehicle' || k === 'car') return 'vehicle'
  if (k === 'track') return 'track'
  return 'other'
})

const objectId = computed(() => {
  const n = Number(props.selection?.id)
  return Number.isFinite(n) ? n : props.selection?.id ?? '-'
})

function fmtNum(raw: string | undefined, digits = 1): string {
  if (raw == null || raw === '') return '—'
  const n = Number(raw)
  return Number.isFinite(n) ? n.toFixed(digits) : raw
}

function pickField(...keys: string[]): string {
  for (const k of keys) {
    const v = fieldMap.value[k]
    if (v != null && v !== '') return v
  }
  return ''
}

function pickStatus(...candidates: string[]): string {
  for (const c of candidates) {
    const hit = statusRows.value.find((r) => r.key === c)
    if (hit?.value) return hit.value
  }
  for (const c of candidates) {
    const hit = statusRows.value.find((r) => r.key.includes(c))
    if (hit?.value) return hit.value
  }
  return ''
}

function isSiteDisabled(): boolean {
  const tags = pickStatus('标记', 'tags')
  if (!tags) return false
  return /unavailable/i.test(tags)
}

const siteInfo = computed(() => ({
  name: pickField('name') || bundleName.value || props.selection?.name || `站点 ${objectId.value}`,
  x: fmtNum(pickField('x'), 1),
  y: fmtNum(pickField('y'), 1),
  z: fmtNum(pickField('z'), 1),
  disabled: isSiteDisabled()
}))

const siteActions = computed(() => {
  const allow = siteActionKeys.value
  if (allow.length) {
    const set = new Set(allow)
    return allMethods.value.filter((m) => set.has(m.methodName))
  }
  return allMethods.value.filter((m) => SITE_ACTION_METHODS.has(m.methodName))
})

const trackActions = computed(() => {
  const allow = trackActionKeys.value
  if (!allow.length) return allMethods.value
  const set = new Set(allow)
  return allMethods.value.filter((m) => set.has(m.methodName))
})

function findCarInList(): Car | undefined {
  if (!props.selection || viewKind.value !== 'vehicle') return undefined
  const sid = String(props.selection.id)
  return props.cars?.find(
    (c) => c.id === sid || String(c.rawId ?? '') === sid || detailIdForCar(c) === sid
  )
}

function detailIdForCar(car: Car): string {
  if (car.rawId != null) return String(car.rawId)
  return car.id.replace(/^C/i, '').replace(/^0+/, '') || car.id
}

/** 站点/路径：仍展示 bundle 返回的状态键值。 */
const statusDisplayRows = computed(() =>
  statusRows.value.map((r) => ({
    key: r.key,
    value: r.value || '—',
    warn: /alarm|error|fault|block|stop/i.test(`${r.key} ${r.value}`) && r.value !== '0' && r.value !== '无'
  }))
)

function getCarTagsText(): string {
  const fromField = pickField('tags')
  if (fromField) return fromField
  const fromStatus = pickStatus('tags', '标记')
  if (fromStatus) return fromStatus
  for (const r of statusRows.value) {
    if (/tag|标记/i.test(r.key) && r.value) return r.value
  }
  return ''
}

function carHasOccupiedTag(): boolean {
  const tags = getCarTagsText()
  if (/occupied/i.test(tags)) return true
  for (const r of statusRows.value) {
    if (/occupied/i.test(r.value ?? '')) return true
  }
  return false
}

function formatBatteryText(): { text: string; low: boolean } {
  // 真实车电量在反射 status「车体_Soc」；投影 batterySoc 常被写死成 0.8，不能优先用。
  const fromStatus = extractSocFromKv(statusRows.value)
  const fromFields = extractSocFromFieldMap(fieldMap.value)
  const car = findCarInList()
  const ratio = fromStatus ?? fromFields ?? (car != null && car.batterySoc > 0 ? car.batterySoc : null)
  if (ratio == null || !Number.isFinite(ratio) || ratio <= 0) return { text: '—', low: false }
  const pct = socToPercent(ratio)
  return { text: `${pct}%`, low: pct < 20 }
}

function formatCarPosition(): string {
  const car = findCarInList()
  const x = pickField('x')
  const y = pickField('y')
  const th = pickField('th', 'theta')
  const px = x || (car != null ? String(car.x) : '')
  const py = y || (car != null ? String(car.y) : '')
  const pth = th || (car != null ? String(car.theta) : '')
  if (px || py) {
    return `(${px || '—'}, ${py || '—'}${pth ? `, ${fmtNum(pth, 1)}°` : ''})`
  }
  const siteId = pickField('siteID')
  if (siteId && siteId !== '-1') return `站点 #${siteId}`
  return pickStatus('位姿', '站点') || '—'
}

function formatTrafficStatus(): string {
  const tc = pickStatus('TCStat') || pickField('TCStat')
  if (tc && tc !== '/' && tc.trim() !== '') return tc

  const blockedRaw = pickStatus('blockedBy')
  if (blockedRaw && blockedRaw !== '[]' && blockedRaw !== '/') {
    try {
      const arr = JSON.parse(blockedRaw) as Array<{ carid?: number; type?: number }>
      if (Array.isArray(arr) && arr.length > 0) {
        const ids = arr.map((b) => b.carid).filter((id): id is number => id != null)
        if (ids.length) return `被车辆 ${ids.join('、')} 交管`
        return '交管阻塞'
      }
    } catch {
      return blockedRaw.length > 48 ? `${blockedRaw.slice(0, 48)}…` : blockedRaw
    }
  }

  const stopped = pickStatus('trafficStopped')
  if (stopped && stopped !== '(0, 0)' && stopped !== '(0,0)' && stopped !== '/') {
    return `交管暂停 ${stopped}`
  }

  const holding = pickStatus('holdingLocks')
  const pending = pickStatus('pendingLocks')
  const parts: string[] = []
  if (holding && holding !== '[]') parts.push(`已占 ${holding}`)
  if (pending && pending !== '[]') parts.push(`待锁 ${pending}`)
  if (parts.length) return parts.join('；')

  return '正常'
}

function formatAlarmStatus(): { text: string; warn: boolean } {
  const fromEnum =
    pickStatus('车体_AlarmInfo', 'AlarmInfo') ||
    statusRows.value.find((r) => /alarminfo/i.test(r.key))?.value
  if (fromEnum && fromEnum !== '0' && fromEnum !== '/' && fromEnum.trim() !== '') {
    return { text: fromEnum, warn: true }
  }
  const lstatus = pickField('lstatus') || pickStatus('lstatus')
  if (lstatus && /故障|报警|异常|失联|超时|检修/i.test(lstatus)) {
    return { text: lstatus, warn: true }
  }
  return { text: '无', warn: false }
}

const vehicleSummary = computed(() => {
  const car = findCarInList()
  const occupied = carHasOccupiedTag()
  const runLabel = occupied ? '任务中' : '空闲'
  const alarm = formatAlarmStatus()
  const battery = formatBatteryText()
  return {
    id: car?.id ?? props.selection?.id ?? '-',
    name: bundleName.value || car?.name || props.selection?.name || '车辆',
    runLabel,
    runTagType: (occupied ? 'success' : 'info') as 'success' | 'info',
    traffic: formatTrafficStatus(),
    alarm: alarm.text,
    alarmWarn: alarm.warn,
    battery: battery.text,
    batteryLow: battery.low,
    position: formatCarPosition()
  }
})

function resolveCarActionWhitelist(): string[] {
  const map = carActionByType.value
  const full = bundleFullTypeName.value
  const short = bundleTypeName.value
  if (full && map[full]?.length) return map[full]!
  if (short && map[short]?.length) return map[short]!
  for (const [k, v] of Object.entries(map)) {
    if (!v.length) continue
    if (full && (k === full || k.endsWith('.' + short) || full.endsWith('.' + k))) return v
    if (short && (k === short || k.endsWith('.' + short))) return v
  }
  return []
}

const carActionWhitelist = computed(() => resolveCarActionWhitelist())

const carActions = computed(() => {
  const allow = carActionWhitelist.value
  if (!allow.length) return []
  const set = new Set(allow)
  return allMethods.value.filter((m) => set.has(m.methodName))
})

/** 运营端只读模式下车辆动作改用运维白名单（按当前账号权限过滤）。 */
const opsCarActions = computed<OpsAction[]>(() =>
  OPS_WHITELIST.filter((o) => o.target === 'car' && auth.hasOp(o.code))
)

async function onOpsExecute(op: OpsAction) {
  const idNum = Number(props.selection?.id)
  if (!Number.isFinite(idNum)) return
  if (op.needConfirm) {
    try {
      await ElMessageBox.confirm(
        `确认执行 [${op.label}]？\n目标车辆：${props.selection?.id}`,
        '二次确认',
        { type: 'warning' }
      )
    } catch {
      return
    }
  }
  executing.value = op.code
  try {
    const resp = await executeOp({ opCode: op.code, targetId: String(idNum), reason: '' })
    if (resp.ok) ElMessage.success(`已执行 ${op.label}（auditId=${resp.auditId}）`)
    else ElMessage.warning(resp.message || `执行未成功：${op.label}`)
  } catch (e) {
    ElMessage.error(`执行失败：${(e as Error).message}`)
  } finally {
    executing.value = null
  }
}

const carActionHint = computed(() => {
  if (!monitorConfigLoaded.value) return '加载配置中…'
  if (!Object.keys(carActionByType.value).length) {
    return '未配置动作。请在「配置中心 → 运营维护」按车型勾选动作并保存。'
  }
  if (!carActionWhitelist.value.length) {
    return '当前车型未配置动作，或方法名与运营维护中勾选的不一致。'
  }
  return '当前车型无可执行动作。'
})

const trackInfo = computed(() => ({
  name: pickField('name') || bundleName.value || props.selection?.name || `路径 ${objectId.value}`
}))

const trackRows = computed(() => [
  { key: 'layer', label: '图层', value: pickField('layerName', 'layer') || '—' },
  { key: 'type', label: '类型', value: bundleTypeName.value || '—' }
])

function shortType(typeName?: string): string {
  if (!typeName) return ''
  const parts = typeName.split('.')
  return parts[parts.length - 1] ?? typeName
}

function selectionKey(sel: SelectedObjectRef | null): string {
  if (!sel) return ''
  return `${sel.kind}:${sel.id}`
}

async function loadMonitorRuntimeConfig(force = false) {
  if (monitorConfigLoaded.value && !force) return
  try {
    const mc = await fetchMonitorConfigCached(force)
    carActionByType.value = { ...(mc.config.carActionByType ?? {}) }
    siteActionKeys.value = [...(mc.config.site?.methods ?? [])]
    trackActionKeys.value = [...(mc.config.track?.methods ?? [])]
  } catch {
    carActionByType.value = {}
    siteActionKeys.value = []
    trackActionKeys.value = []
  } finally {
    monitorConfigLoaded.value = true
  }
}

type BundlePayload = Awaited<ReturnType<typeof reflectionApi.getBundle>>

function applyBundle(bundle: BundlePayload, fallbackName?: string) {
  bundleName.value = bundle.summary?.name ?? fallbackName ?? ''
  bundleTypeName.value = bundle.typeName ?? ''
  bundleFullTypeName.value =
    ('fullTypeName' in bundle && bundle.fullTypeName) ? bundle.fullTypeName : bundle.typeName ?? ''
  fieldMap.value = { ...bundle.fields }
  for (const f of bundle.fieldList ?? []) {
    fieldMap.value[f.key] = f.value
  }
  statusRows.value = bundle.status ?? []
  allMethods.value = bundle.methods ?? []
}

/** 车辆列表已有投影数据时先展示，bundle 返回后再补齐。 */
function applyCarListPreview(): boolean {
  const car = findCarInList()
  if (!car) return false
  bundleName.value = car.name
  bundleTypeName.value = car.typeName ?? ''
  bundleFullTypeName.value = car.typeName ?? ''
  fieldMap.value = {
    x: String(car.x),
    y: String(car.y),
    th: String(car.theta)
  }
  statusRows.value = []
  allMethods.value = []
  return true
}

async function loadAll() {
  if (!props.selection) {
    bundleName.value = ''
    fieldMap.value = {}
    statusRows.value = []
    allMethods.value = []
    hydrated.value = false
    return
  }
  const rk = reflectionKind.value
  const idNum = Number(props.selection.id)
  if (!rk || !Number.isFinite(idNum)) return

  const soft = hydrated.value
  const listPreview = !soft && viewKind.value === 'vehicle' && applyCarListPreview()
  if (listPreview) hydrated.value = true
  if (!soft) initialLoading.value = !listPreview
  else actionsLoading.value = true

  try {
    if (props.readOnly) {
      // 运营端只读：只取 bundle 展示详情，不加载 reflection 动作/运营配置；
      // 失败时静默降级，由 findCarInList() 用 cars 列表数据兜底。
      const bundle = await reflectionApi.getBundle(rk, idNum)
      applyBundle(bundle, props.selection.name ?? '')
      allMethods.value = []
      hydrated.value = true
      return
    }
    await loadMonitorRuntimeConfig(false)
    const bundle = await reflectionApi.getBundle(rk, idNum)
    applyBundle(bundle, props.selection.name ?? '')
    hydrated.value = true
  } catch (e) {
    if (props.readOnly) {
      console.warn('[MonitorSelection] 只读详情加载失败，降级用列表数据', e)
    } else {
      ElMessage.error(`加载选中信息失败：${(e as Error).message}`)
    }
  } finally {
    initialLoading.value = false
    actionsLoading.value = false
  }
}

async function onExecute(m: ReflectionMethod) {
  const rk = reflectionKind.value
  if (!rk || !props.selection || !m.methodName) return
  const idNum = Number(props.selection.id)
  if (!Number.isFinite(idNum)) return

  if (rk === 'car' && methodNeedsSitePick(m)) {
    pendingGotoMethod.value = m
    sitePickTitle.value = `选择目标站点 — ${m.label || m.methodName}`
    executing.value = m.methodName
    try {
      const siteId = await pickTargetSiteOnMap()
      if (siteId != null) {
        await onSitePickConfirm(siteId)
        return
      }
      // 取消地图拾取时回退到站点列表对话框
      sitePickSites.value = await loadSitePickOptions()
      sitePickOpen.value = true
    } catch (e) {
      ElMessage.error(`加载站点列表失败：${(e as Error).message}`)
    } finally {
      if (!sitePickOpen.value) executing.value = null
    }
    return
  }

  executing.value = m.methodName
  try {
    const ok = await executeReflectionMethod(rk, idNum, m)
    if (ok) await loadAll()
  } finally {
    executing.value = null
  }
}

async function onSitePickConfirm(siteId: number) {
  const idNum = Number(props.selection?.id)
  if (!Number.isFinite(idNum)) return
  executing.value = pendingGotoMethod.value?.methodName ?? 'goto'
  try {
    const ok = await executeCarGotoSite(idNum, siteId)
    if (ok) await loadAll()
  } finally {
    executing.value = null
    pendingGotoMethod.value = null
  }
}

async function onSitePickOnMap() {
  const siteId = await pickTargetSiteOnMap()
  sitePickOpen.value = false
  if (siteId != null) await onSitePickConfirm(siteId)
}

let loadToken = 0
let lastLoadedSelectionKey = ''
watch(
  () => [selectionKey(props.selection), props.refreshKey] as const,
  async (curr, prev) => {
    const token = ++loadToken
    // refreshKey 单独变化（如运营维护保存动作白名单）：只刷新配置，不重复拉 bundle。
    if (prev && curr[0] === prev[0] && curr[0] && curr[1] !== prev[1]) {
      await loadMonitorRuntimeConfig(true)
      if (token !== loadToken) return
      return
    }
    if (curr[0] !== lastLoadedSelectionKey) {
      hydrated.value = false
      lastLoadedSelectionKey = curr[0]
    }
    await loadAll()
    if (token !== loadToken) return
  },
  { immediate: true }
)

</script>

<style scoped>
.monitor-selection {
  display: flex;
  flex-direction: column;
  min-height: 0;
  height: 100%;
}
.pane {
  flex: 1;
  min-height: 0;
  overflow: auto;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding-right: 2px;
}
.pane-loading {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 32px;
  color: var(--mg-text-muted);
  font-size: 13px;
}
.empty-hint {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 24px 8px;
  text-align: center;
}
.empty-title { font-size: 13px; color: var(--mg-text-light); font-weight: 500; }
.empty-desc { font-size: 11.5px; color: var(--mg-text-dim); }

.card {
  background: var(--mg-veil-1);
  border: 1px solid var(--mg-veil-border);
  border-radius: 8px;
  padding: 8px 10px;
  flex: none;
}
.card--header {
  background: linear-gradient(140deg, rgba(170, 110, 250, 0.2) 0%, rgba(70, 30, 110, 0.08) 100%);
  border-color: rgba(170, 110, 250, 0.28);
}
.card--actions {
  background: rgba(140, 80, 200, 0.08);
  border-color: rgba(170, 110, 250, 0.22);
}
.card-hd {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 6px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.8px;
  color: var(--mg-text-muted);
  text-transform: uppercase;
}
.card-hd .meta {
  font-size: 10px;
  font-weight: 400;
  color: var(--mg-text-dim);
  text-transform: none;
  letter-spacing: 0;
}

.title-row {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 4px;
}
.id-tag {
  font-size: 11px;
  font-weight: 600;
  padding: 1px 7px;
  border-radius: 4px;
  background: var(--mg-primary);
  color: #fff;
}
.type-tag {
  font-size: 11px;
  color: var(--mg-text-muted);
}
.name-line {
  font-size: 14px;
  font-weight: 600;
  color: var(--mg-text-light);
  line-height: 1.3;
  word-break: break-all;
}
.sub-line {
  margin-top: 2px;
  font-size: 11px;
  color: var(--mg-text-muted);
  font-family: ui-monospace, Consolas, monospace;
}

.kv-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 6px;
}
.kv-list { display: flex; flex-direction: column; gap: 2px; }
.kv-row {
  display: grid;
  grid-template-columns: minmax(72px, 38%) 1fr;
  gap: 8px;
  align-items: start;
  font-size: 12px;
  line-height: 1.35;
  padding: 4px 0;
  border-bottom: 1px solid var(--mg-divider);
}
.kv-row:last-child { border-bottom: none; }
.kv-row.is-warn .v { color: var(--mg-status-danger); }
.kv-row .v.is-low-battery { color: var(--mg-status-danger); }
.kv {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.k, .kv-row .k {
  color: var(--mg-text-muted);
  font-size: 11px;
  word-break: break-word;
}
.v, .kv-row .v {
  color: var(--mg-text-light);
  word-break: break-word;
  font-size: 12px;
}
.mono { font-family: ui-monospace, Consolas, monospace; font-size: 11.5px; }

.action-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 6px;
}
.action-grid--vehicle {
  grid-template-columns: repeat(auto-fill, minmax(118px, 1fr));
}
.action-btn {
  appearance: none;
  cursor: pointer;
  border: 1px solid var(--mg-action-border);
  background: var(--mg-action-bg);
  color: #fff;
  font-size: 12px;
  font-weight: 600;
  padding: 7px 8px;
  border-radius: 6px;
  transition: background 0.14s ease, border-color 0.14s ease, box-shadow 0.14s ease;
  text-align: center;
  line-height: 1.25;
}
.action-btn--goto {
  border-color: rgba(250, 200, 120, 0.65);
  background: rgba(120, 70, 20, 0.45);
}
.action-btn:hover:not(:disabled) {
  background: var(--mg-action-bg-hover);
  border-color: var(--mg-action-border-hi);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
}
.action-btn--goto:hover:not(:disabled) {
  background: rgba(160, 100, 30, 0.75);
  border-color: rgba(255, 220, 150, 0.9);
}
.action-btn:disabled { opacity: 0.55; cursor: progress; }

/* fame-lavender 浅紫主题：goto（前往站点）按钮在白底下需实色琥珀 + 白字，
   否则半透明棕底叠白底→浅棕 + 白字不可读。 */
:root[data-shell="outpost"] .action-btn--goto {
  border-color: #b45309;
  background: #b45309;
}
:root[data-shell="outpost"] .action-btn--goto:hover:not(:disabled) {
  border-color: #92400e;
  background: #92400e;
}

.muted-line {
  font-size: 11.5px;
  color: var(--mg-text-dim);
  line-height: 1.4;
  padding: 2px 0;
}
</style>
