<template>
  <div class="edit-property-panel">
    <el-tabs v-model="tab" class="prop-tabs">
      <el-tab-pane label="对象" name="object">
        <div v-if="selectionCount === 0" class="empty-hint">
          <el-icon class="empty-icon"><InfoFilled /></el-icon>
          <span class="empty-title">未选中对象</span>
          <span class="empty-desc">在画布上点击或框选以编辑属性。</span>
        </div>

        <div v-else-if="loading" class="loading-hint">
          <el-icon class="is-loading"><Loading /></el-icon>
          <span>加载属性…</span>
        </div>

        <div v-else class="object-pane">
          <!-- Header card：ID / 类型 / 名称 / 图层 / 删除按钮 -->
          <section class="prop-card prop-card--header">
            <div class="prop-card-row">
              <div class="prop-id-block">
                <span class="prop-id-tag">{{ selectionCount > 1 ? `多选 ${selectionCount}` : `#${primary?.id ?? '-'}` }}</span>
                <span class="prop-type-tag">{{ primary?.typeName ?? '-' }}</span>
              </div>
              <div class="prop-header-actions">
                <el-tag
                  v-if="primary?.status"
                  :type="statusTagType"
                  size="small"
                  effect="dark"
                  class="prop-status-tag"
                >
                  {{ primary.status }}
                </el-tag>
                <el-tooltip
                  :content="selectionCount > 1 ? `删除选中的 ${selectionCount} 个对象（可 Ctrl+Z 撤销）` : '删除该对象（可 Ctrl+Z 撤销）'"
                  placement="top"
                  :show-after="220"
                >
                  <button
                    type="button"
                    class="prop-delete-btn"
                    :disabled="deleting"
                    @click="onDelete"
                  >
                    <span class="prop-delete-icon">{{ deleting ? '⟳' : '🗑' }}</span>
                    <span>删除</span>
                  </button>
                </el-tooltip>
              </div>
            </div>

            <div class="prop-card-row prop-name-row">
              <label class="prop-name-label">名称</label>
              <el-input
                v-if="primary"
                :model-value="editingName"
                size="small"
                placeholder="对象名"
                @update:model-value="(v: string) => editingName = v"
                @blur="onPrimaryNameBlur"
                @change="onPrimaryNameSubmit"
              />
            </div>

            <div class="prop-card-row prop-layer-row">
              <label class="prop-name-label">图层</label>
              <el-select
                v-if="primary"
                :model-value="primary.layer ?? ''"
                size="small"
                placeholder="—"
                class="prop-layer-select"
                @update:model-value="(v: any) => onPrimaryLayerChange(v)"
              >
                <el-option label="（无）" value="" />
                <el-option v-for="l in layers" :key="l" :label="l" :value="l" />
              </el-select>
            </div>
          </section>

          <!-- 几何 / 位置：x, y, z, size, angle, rotation, scale, radius, siteA, siteB, ... -->
          <section v-if="positionFields.length > 0" class="prop-card prop-card--geom">
            <header class="prop-card-header">
              <span class="prop-card-title">位置 / 几何</span>
              <span class="prop-card-flag" :class="{ 'is-synced': syncFlash }" :title="syncFlash ? '已同步到画布' : '改动会立即同步到 SimpleLite 画布'">
                <span class="prop-card-flag-dot" />
                <span class="prop-card-flag-text">{{ syncFlash ? '已同步' : '同步到画布' }}</span>
              </span>
              <span class="prop-card-meta">{{ positionFields.length }} 项</span>
            </header>
            <div class="prop-grid">
              <div v-for="f in positionFields" :key="f.key" class="prop-grid-item">
                <label class="prop-field-label">
                  {{ fieldDisplayLabel(f.key) }}
                  <span v-if="fieldUnit(f.key)" class="prop-field-unit">{{ fieldUnit(f.key) }}</span>
                </label>
                <el-input-number
                  v-if="isNumberField(f.key)"
                  :model-value="numericValue(f.value)"
                  :controls="false"
                  :step="numericStep(f.key)"
                  size="small"
                  class="prop-field-num"
                  @change="(v: number | undefined) => onFieldChange(f.key, v == null ? '' : String(v))"
                />
                <el-input
                  v-else
                  :model-value="f.value"
                  size="small"
                  spellcheck="false"
                  @change="(v: string) => onFieldChange(f.key, v)"
                />
              </div>
            </div>
          </section>

          <!-- 样式：颜色、显示开关 -->
          <section v-if="styleFields.length > 0" class="prop-card prop-card--style">
            <header class="prop-card-header">
              <span class="prop-card-title">样式 / 外观</span>
              <span class="prop-card-meta">{{ styleFields.length }} 项</span>
            </header>
            <div class="prop-grid">
              <div v-for="f in styleFields" :key="f.key" class="prop-grid-item">
                <label class="prop-field-label">{{ fieldDisplayLabel(f.key) }}</label>
                <el-color-picker
                  v-if="isColorField(f.key, f.value)"
                  :model-value="f.value || '#ffffff'"
                  show-alpha
                  size="small"
                  @change="(v: string | null) => onFieldChange(f.key, v ?? '')"
                />
                <el-switch
                  v-else-if="isBooleanField(f.key, f.value)"
                  :model-value="parseBool(f.value)"
                  size="small"
                  @change="(v: any) => onFieldChange(f.key, v ? 'true' : 'false')"
                />
                <el-input
                  v-else
                  :model-value="f.value"
                  size="small"
                  spellcheck="false"
                  @change="(v: string) => onFieldChange(f.key, v)"
                />
              </div>
            </div>
          </section>

          <!-- 其它 / 自定义字段（兜底，可添加） -->
          <section class="prop-card prop-card--custom">
            <header class="prop-card-header">
              <span class="prop-card-title">其它字段 / 自定义</span>
              <span class="prop-card-meta">{{ otherFields.length }} 项</span>
            </header>
            <div v-if="otherFields.length > 0" class="prop-grid">
              <div v-for="f in otherFields" :key="f.key" class="prop-grid-item prop-grid-item--wide">
                <label class="prop-field-label" :title="f.key">{{ f.key }}</label>
                <div class="prop-field-row">
                  <el-input
                    :model-value="f.value"
                    size="small"
                    spellcheck="false"
                    @change="(v: string) => onFieldChange(f.key, v)"
                  />
                  <el-tooltip content="删除该字段" placement="top">
                    <button
                      class="prop-field-icon-btn"
                      @click="onFieldChange(f.key, '')"
                    >×</button>
                  </el-tooltip>
                </div>
              </div>
            </div>
            <div v-else class="prop-empty-row">无其它字段</div>
            <button class="prop-add-field-btn" @click="onAddField">＋ 添加字段</button>
          </section>

          <!-- 运行状态（只读） -->
          <section v-if="status.length > 0" class="prop-card prop-card--status">
            <header class="prop-card-header">
              <span class="prop-card-title">运行状态</span>
              <span class="prop-card-meta">{{ status.length }} 项</span>
            </header>
            <div class="prop-status-list">
              <div v-for="s in status" :key="s.key" class="prop-status-item">
                <span class="prop-status-key">{{ s.key }}</span>
                <span class="prop-status-value" :title="s.value">{{ s.value }}</span>
              </div>
            </div>
          </section>

          <!-- 操作 -->
          <section v-if="methods.length > 0" class="prop-card prop-card--actions">
            <header class="prop-card-header">
              <span class="prop-card-title">操作</span>
              <span class="prop-card-meta">{{ methods.length }} 项</span>
            </header>
            <div class="prop-actions">
              <el-tooltip
                v-for="m in methods"
                :key="m.methodName"
                :content="m.description || m.label || m.methodName"
                placement="top"
                :show-after="220"
              >
                <button
                  class="prop-action-btn"
                  :disabled="actionBusy === m.methodName"
                  @click="onAction(m.methodName)"
                >
                  <span v-if="actionBusy === m.methodName" class="prop-action-spinner">⟳</span>
                  <span class="prop-action-icon">▶</span>
                  <span class="prop-action-label">{{ m.label || m.methodName }}</span>
                </button>
              </el-tooltip>
            </div>
          </section>
        </div>
      </el-tab-pane>

      <el-tab-pane label="类型默认" name="defaults">
        <el-form label-width="120px" size="small" class="defaults-form">
          <el-alert type="info" :closable="false">
            为「新建站点 / 新建路径」预设默认样式与字段。保存后下次创建即生效。
          </el-alert>
          <el-divider content-position="left">站点</el-divider>
          <el-form-item label="默认图层">
            <el-input v-model="defaults.site.layer" />
          </el-form-item>
          <el-form-item label="名字前缀">
            <el-input v-model="defaults.site.namePrefix" />
          </el-form-item>
          <el-form-item label="默认颜色">
            <el-color-picker v-model="defaults.site.color" show-alpha />
          </el-form-item>
          <el-divider content-position="left">路径</el-divider>
          <el-form-item label="默认图层">
            <el-input v-model="defaults.track.layer" />
          </el-form-item>
          <el-form-item label="默认线宽">
            <el-input-number v-model="defaults.track.lineWidth" :min="1" :max="20" />
          </el-form-item>
          <el-form-item label="默认颜色">
            <el-color-picker v-model="defaults.track.color" show-alpha />
          </el-form-item>
          <el-button size="small" type="primary" @click="onSaveDefaults">保存默认</el-button>
        </el-form>
      </el-tab-pane>

      <el-tab-pane label="图层" name="layers">
        <el-table :data="layerRows" size="small" class="layer-table">
          <el-table-column prop="name" label="图层" width="120" />
          <el-table-column label="可见" width="60">
            <template #default="{ row }">
              <el-switch v-model="row.visible" @change="onLayerToggle(row)" />
            </template>
          </el-table-column>
          <el-table-column label="可选" width="60">
            <template #default="{ row }">
              <el-switch v-model="row.selectable" @change="onLayerToggle(row)" />
            </template>
          </el-table-column>
          <el-table-column label="颜色">
            <template #default="{ row }">
              <el-color-picker v-model="row.color" @change="onLayerToggle(row)" />
            </template>
          </el-table-column>
        </el-table>
        <el-input v-model="newLayerName" size="small" placeholder="新图层名" class="new-layer">
          <template #append>
            <el-button @click="onAddLayer">添加</el-button>
          </template>
        </el-input>
      </el-tab-pane>
    </el-tabs>
  </div>
</template>

<script setup lang="ts">
import { InfoFilled, Loading } from '@element-plus/icons-vue'
import { computed, ref, watch } from 'vue'
import type { ReflectionMethod, ReflectionObject } from '@/api/reflection'

interface PropFieldKv { key: string; value: string }
interface LayerRow { name: string; visible: boolean; selectable: boolean; color: string }

const props = defineProps<{
  selectionCount: number
  primary: ReflectionObject | null
  fields: PropFieldKv[]
  status: PropFieldKv[]
  methods: ReflectionMethod[]
  loading: boolean
  layers: string[]
  defaults: {
    site: { layer: string; namePrefix: string; color: string }
    track: { layer: string; lineWidth: number; color: string }
  }
  layerRows: LayerRow[]
}>()

const emit = defineEmits<{
  (e: 'rename', name: string): void
  (e: 'change-layer', layer: string): void
  (e: 'set-field', key: string, value: string): void
  (e: 'add-field'): void
  (e: 'exec-action', method: string): void
  (e: 'save-defaults'): void
  (e: 'layer-toggle', row: LayerRow): void
  (e: 'add-layer', name: string): void
  (e: 'delete'): void
}>()

const tab = ref<'object' | 'defaults' | 'layers'>('object')
const actionBusy = ref<string | null>(null)
const newLayerName = ref('')
const deleting = ref(false)
const syncFlash = ref(false)
let syncFlashTimer: ReturnType<typeof setTimeout> | null = null
function flashSync() {
  syncFlash.value = true
  if (syncFlashTimer) clearTimeout(syncFlashTimer)
  syncFlashTimer = setTimeout(() => { syncFlash.value = false }, 1200)
}

// ──────────────────────────────────────────────────────────────────────────
// 字段分组
//
// 后端 ReflectionApiController/MapEditApiController 对每个被 [FieldMember] 标的
// public 字段都吐出来（含 `id / name / layerName / x / y / z / color /
// displaySetting / radius / direction / typeInfo / siteA / siteB / mustFree / ...`）。
// 这里的策略：
//   - 顶部 Header 卡片已经覆盖 id / name / layerName / status，所以从 fields 中剔除这些；
//   - 「位置 / 几何」吃掉所有跟坐标 / 角度 / 大小 / 半径 / 端点站点强相关的 key；
//   - 「样式 / 外观」吃掉 color / displaySetting / direction / typeInfo / mustFree / 文字渲染相关；
//   - 剩下的统统进「其它字段」（含用户 [FieldMember] 自定义、tag 反序、随便加上去的 free-form key）。
// 这样不管后端塞什么字段进来都能露出来 + 可编辑。
// ──────────────────────────────────────────────────────────────────────────

const HEADER_KEYS = new Set(['id', 'name', 'layerName'])

const POSITION_KEYS = new Set([
  'x', 'y', 'z',
  'cx', 'cy', 'cz',
  'dx', 'dy', 'dz',
  'sx', 'sy', 'ex', 'ey',
  'startX', 'startY', 'endX', 'endY',
  'size', 'width', 'height', 'depth',
  'angle', 'rotation', 'theta',
  'scale',
  'radius', 'r',
  'siteA', 'siteB'
])

const STYLE_KEYS = new Set([
  'color',
  'displaySetting',
  'direction',
  'typeInfo',
  'mustFree',
  'imagePath',
  'modelPath',
  'fontFamily',
  'fontSize',
  'textColor',
  'lineWidth',
  'opacity'
])

const FIELD_LABELS: Record<string, string> = {
  x: 'X',
  y: 'Y',
  z: 'Z',
  size: '大小',
  width: '宽',
  height: '高',
  depth: '厚',
  angle: '角度',
  rotation: '旋转',
  theta: 'θ',
  scale: '缩放',
  radius: '半径',
  siteA: '起点站',
  siteB: '终点站',
  startX: '起点 X',
  startY: '起点 Y',
  endX: '终点 X',
  endY: '终点 Y',
  cx: '中心 X',
  cy: '中心 Y',
  color: '颜色',
  displaySetting: '显示设置',
  direction: '方向',
  typeInfo: '类型信息',
  mustFree: '必须互斥',
  imagePath: '图片路径',
  modelPath: '模型路径',
  fontSize: '字号',
  textColor: '文字色',
  lineWidth: '线宽',
  opacity: '透明度'
}

const FIELD_UNITS: Record<string, string> = {
  x: 'mm', y: 'mm', z: 'mm',
  cx: 'mm', cy: 'mm', cz: 'mm',
  dx: 'mm', dy: 'mm', dz: 'mm',
  startX: 'mm', startY: 'mm',
  endX: 'mm', endY: 'mm',
  width: 'mm', height: 'mm', depth: 'mm',
  size: 'mm', radius: 'mm',
  angle: '°', rotation: '°', theta: '°',
  fontSize: 'px', lineWidth: 'px'
}

const editableFields = computed(() => props.fields.filter((f) => !HEADER_KEYS.has(f.key)))

const positionFields = computed(() => editableFields.value.filter((f) => POSITION_KEYS.has(f.key)))
const styleFields = computed(() => editableFields.value.filter((f) => STYLE_KEYS.has(f.key)))
const otherFields = computed(() =>
  editableFields.value.filter((f) => !POSITION_KEYS.has(f.key) && !STYLE_KEYS.has(f.key))
)

function fieldDisplayLabel(key: string): string {
  return FIELD_LABELS[key] ?? key
}
function fieldUnit(key: string): string {
  return FIELD_UNITS[key] ?? ''
}

function numericValue(v: string): number | undefined {
  if (v == null || v === '') return undefined
  const n = Number(v)
  return Number.isFinite(n) ? n : undefined
}
function numericStep(key: string): number {
  if (key.toLowerCase().includes('angle') || key.toLowerCase().includes('rotation') || key === 'theta') return 1
  if (key === 'scale' || key === 'opacity') return 0.1
  return 10
}
function isNumberField(key: string): boolean {
  return POSITION_KEYS.has(key)
}
function isColorField(key: string, value: string): boolean {
  if (key === 'color' || key === 'textColor') return true
  return /^#[0-9a-fA-F]{3,8}$/.test(value)
}
function isBooleanField(_key: string, value: string): boolean {
  if (value === 'true' || value === 'false' || value === 'True' || value === 'False') return true
  return false
}
function parseBool(v: string): boolean {
  return v === 'true' || v === 'True' || v === '1'
}

const statusTagType = computed<'success' | 'warning' | 'danger' | 'info'>(() => {
  const s = (props.primary?.status ?? '').toLowerCase()
  if (!s) return 'info'
  if (s.includes('error') || s.includes('alarm') || s.includes('failed')) return 'danger'
  if (s.includes('warn') || s.includes('idle')) return 'warning'
  if (s.includes('online') || s.includes('running') || s.includes('ready') || s.includes('ok')) return 'success'
  return 'info'
})

// 本地名称缓冲：避免 v-model 直接 mutate 父级传下的 primary props（违反 vue/no-mutating-props）。
// 父级 primary 变化时同步进 editingName；用户改完按回车/失焦时 emit('rename') 向上抛新值。
const editingName = ref<string>('')
watch(() => props.primary?.name ?? '', (n) => { editingName.value = n }, { immediate: true })

function onPrimaryNameBlur() { onPrimaryNameSubmit() }
function onPrimaryNameSubmit() {
  if (!props.primary) return
  const next = editingName.value
  if (next === (props.primary.name ?? '')) return
  emit('rename', next)
}
function onPrimaryLayerChange(v: string) {
  emit('change-layer', v)
}
function onFieldChange(key: string, value: string) {
  emit('set-field', key, value)
  if (POSITION_KEYS.has(key)) flashSync()
}
function onAddField() { emit('add-field') }
async function onAction(method: string) {
  try {
    actionBusy.value = method
    emit('exec-action', method)
  } finally {
    actionBusy.value = null
  }
}
function onSaveDefaults() { emit('save-defaults') }
function onLayerToggle(row: LayerRow) { emit('layer-toggle', row) }
function onAddLayer() {
  const n = newLayerName.value.trim()
  if (!n) return
  emit('add-layer', n)
  newLayerName.value = ''
}

function onDelete() {
  if (props.selectionCount === 0 || deleting.value) return
  // 仅置 busy；真正的删除由父组件 deleteSelection 走 history.run，
  // SSE object-deleted 回来后 selection 自动清空、本面板会被父组件解除。
  // 这里 100ms 后自动放开按钮，避免父组件 await 期间用户疯狂双击导致重复入栈。
  deleting.value = true
  emit('delete')
  setTimeout(() => { deleting.value = false }, 600)
}
</script>

<style scoped>
.edit-property-panel {
  height: 100%;
  width: 360px;
  background: linear-gradient(180deg, rgba(28, 12, 56, 0.85) 0%, rgba(18, 6, 36, 0.95) 100%);
  border-left: 1px solid rgba(255,255,255,0.10);
  padding: 0;
  backdrop-filter: blur(10px);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  color: rgba(232,215,245,0.92);
}
.prop-tabs {
  height: 100%;
  display: flex;
  flex-direction: column;
}
.prop-tabs :deep(.el-tabs__header) {
  margin: 0;
  padding: 0 8px;
  border-bottom: 1px solid rgba(255,255,255,0.08);
}
.prop-tabs :deep(.el-tabs__nav-wrap)::after {
  background: transparent;
}
.prop-tabs :deep(.el-tabs__item) {
  color: rgba(220, 200, 240, 0.7);
  font-weight: 500;
  font-size: 13px;
  height: 36px;
  line-height: 36px;
}
.prop-tabs :deep(.el-tabs__item.is-active) {
  color: #fff;
}
.prop-tabs :deep(.el-tabs__active-bar) {
  background: linear-gradient(90deg, rgba(170, 110, 250, 1) 0%, rgba(255, 110, 220, 1) 100%);
  height: 2px;
}
.prop-tabs :deep(.el-tabs__content) {
  flex: 1;
  overflow: auto;
  padding: 10px 12px 14px;
}

.empty-hint {
  display: flex; align-items: center; gap: 6px;
  padding: 56px 16px; text-align: center;
  color: rgba(220, 200, 240, 0.55);
  flex-direction: column;
}
.empty-icon { font-size: 28px; color: rgba(180, 130, 220, 0.45); }
.empty-title { font-size: 13px; color: rgba(232, 215, 245, 0.75); font-weight: 500; }
.empty-desc { font-size: 11.5px; color: rgba(220, 200, 240, 0.5); }
.loading-hint {
  display: flex; align-items: center; gap: 8px;
  padding: 32px 16px; text-align: center;
  color: rgba(232,215,245,0.65); font-size: 12.5px;
  justify-content: center;
}

.object-pane {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.prop-card {
  background: rgba(255,255,255,0.04);
  border: 1px solid rgba(255,255,255,0.08);
  border-radius: 8px;
  padding: 10px 12px;
  box-shadow: 0 1px 4px rgba(0,0,0,0.20);
}
.prop-card--header {
  background: linear-gradient(140deg, rgba(170, 110, 250, 0.18) 0%, rgba(70, 30, 110, 0.10) 100%);
  border-color: rgba(170, 110, 250, 0.28);
}
.prop-card--geom {
  background: linear-gradient(140deg, rgba(80, 140, 220, 0.10) 0%, rgba(40, 70, 130, 0.06) 100%);
  border-color: rgba(120, 170, 230, 0.22);
}
.prop-card--style {
  background: linear-gradient(140deg, rgba(220, 110, 200, 0.10) 0%, rgba(150, 70, 130, 0.06) 100%);
  border-color: rgba(230, 130, 200, 0.22);
}
.prop-card--custom {
  background: rgba(255, 255, 255, 0.03);
  border-color: rgba(170, 110, 250, 0.18);
}
.prop-card--status { background: rgba(80, 140, 220, 0.06); border-color: rgba(120, 170, 230, 0.18); }
.prop-card--actions { background: rgba(140, 80, 200, 0.06); border-color: rgba(170, 110, 250, 0.18); }

.prop-card-header {
  display: flex; align-items: center; justify-content: space-between;
  margin-bottom: 8px;
}
.prop-card-title {
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 1px;
  color: rgba(220, 180, 250, 0.85);
  text-transform: uppercase;
}
.prop-card-meta {
  font-size: 10.5px;
  color: rgba(200, 180, 220, 0.5);
}
.prop-card-flag {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 10.5px;
  color: rgba(180, 200, 240, 0.6);
  padding: 2px 7px;
  border-radius: 10px;
  background: rgba(120, 160, 220, 0.10);
  transition: background .25s ease, color .25s ease, box-shadow .25s ease;
  margin-left: auto;
}
.prop-card-flag.is-synced {
  background: linear-gradient(120deg, rgba(80, 200, 130, 0.32), rgba(120, 220, 200, 0.32));
  color: rgba(220, 255, 230, 0.95);
  box-shadow: 0 0 8px rgba(80, 200, 130, 0.45);
}
.prop-card-flag-dot {
  width: 6px; height: 6px; border-radius: 50%;
  background: rgba(180, 200, 240, 0.6);
  transition: background .25s ease, box-shadow .25s ease;
}
.prop-card-flag.is-synced .prop-card-flag-dot {
  background: #6ce7a8;
  box-shadow: 0 0 6px rgba(108, 231, 168, 0.85);
}
.prop-card-row {
  display: flex; align-items: center; justify-content: space-between;
  margin-bottom: 6px;
}
.prop-card-row:last-child { margin-bottom: 0; }

.prop-id-block { display: flex; align-items: center; gap: 6px; }
.prop-id-tag {
  background: rgba(170, 110, 250, 0.30);
  color: #fff;
  padding: 2px 8px;
  font-size: 11.5px;
  font-weight: 600;
  border-radius: 4px;
  letter-spacing: 0.5px;
}
.prop-type-tag {
  color: rgba(232, 215, 245, 0.75);
  font-size: 11.5px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
.prop-status-tag { font-weight: 500; }

.prop-header-actions {
  display: flex;
  align-items: center;
  gap: 6px;
}

.prop-delete-btn {
  appearance: none;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 3px 9px;
  border-radius: 5px;
  font-size: 11.5px;
  font-weight: 500;
  letter-spacing: 0.4px;
  color: rgba(255, 200, 210, 0.95);
  background: linear-gradient(135deg, rgba(255, 90, 120, 0.18) 0%, rgba(220, 60, 110, 0.22) 100%);
  border: 1px solid rgba(255, 110, 140, 0.45);
  transition: background .14s ease, border-color .14s ease, color .14s ease, box-shadow .14s ease, transform .12s ease;
}
.prop-delete-btn:hover:not(:disabled) {
  background: linear-gradient(135deg, rgba(255, 90, 120, 0.55) 0%, rgba(220, 60, 110, 0.65) 100%);
  border-color: rgba(255, 140, 170, 0.85);
  color: #fff;
  box-shadow: 0 3px 10px rgba(220, 70, 110, 0.45);
}
.prop-delete-btn:active:not(:disabled) { transform: scale(0.96); }
.prop-delete-btn:disabled { opacity: 0.55; cursor: progress; }
.prop-delete-icon { font-size: 12px; line-height: 1; }

.prop-name-row, .prop-layer-row { gap: 8px; }
.prop-name-label {
  width: 36px;
  font-size: 11.5px;
  color: rgba(200, 180, 220, 0.7);
  flex: none;
}
.prop-layer-select { flex: 1; }

.prop-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  column-gap: 8px;
  row-gap: 8px;
}
.prop-grid-item { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.prop-grid-item--wide { grid-column: 1 / -1; }
.prop-field-label {
  font-size: 11px;
  color: rgba(200, 180, 230, 0.78);
  letter-spacing: 0.3px;
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 4px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.prop-field-unit {
  font-size: 10px;
  color: rgba(170, 150, 200, 0.55);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  flex: none;
}
.prop-field-num :deep(.el-input-number) { width: 100%; }
.prop-field-num :deep(.el-input__wrapper) {
  background: rgba(255,255,255,0.06);
  box-shadow: inset 0 0 0 1px rgba(170, 110, 250, 0.18);
}
.prop-field-row { display: flex; align-items: center; gap: 4px; }
.prop-field-icon-btn {
  appearance: none; border: 0; cursor: pointer;
  width: 22px; height: 22px;
  border-radius: 4px;
  background: rgba(255, 100, 130, 0.12);
  color: rgba(255, 180, 200, 0.85);
  font-size: 14px; line-height: 1;
  display: inline-flex; align-items: center; justify-content: center;
  flex: none;
  transition: background .14s, color .14s;
}
.prop-field-icon-btn:hover {
  background: rgba(255, 100, 130, 0.32);
  color: #fff;
}

.prop-empty-row {
  font-size: 11.5px;
  color: rgba(200, 180, 220, 0.4);
  text-align: center;
  padding: 8px 0;
}
.prop-add-field-btn {
  appearance: none; border: 0; cursor: pointer;
  width: 100%;
  margin-top: 8px;
  padding: 6px 10px;
  border-radius: 6px;
  background: rgba(170, 110, 250, 0.14);
  border: 1px dashed rgba(170, 110, 250, 0.40);
  color: rgba(220, 180, 250, 0.92);
  font-size: 12px;
  font-weight: 500;
  transition: background .14s ease, border-color .14s ease, color .14s ease;
}
.prop-add-field-btn:hover {
  background: rgba(170, 110, 250, 0.26);
  border-color: rgba(170, 110, 250, 0.65);
  color: #fff;
}

.prop-status-list { display: flex; flex-direction: column; gap: 4px; }
.prop-status-item {
  display: flex; justify-content: space-between; align-items: center;
  padding: 5px 8px;
  border-radius: 4px;
  background: rgba(255,255,255,0.03);
  font-size: 11.5px;
}
.prop-status-key { color: rgba(170, 200, 240, 0.85); font-weight: 500; }
.prop-status-value {
  color: rgba(232, 245, 255, 0.95);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  max-width: 60%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.prop-actions {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 6px;
}
.prop-action-btn {
  appearance: none; border: 0; cursor: pointer;
  padding: 7px 10px;
  border-radius: 7px;
  background: linear-gradient(135deg, rgba(170, 110, 250, 0.20) 0%, rgba(120, 70, 220, 0.20) 100%);
  border: 1px solid rgba(170, 110, 250, 0.35);
  color: rgba(232, 215, 245, 0.95);
  font-size: 12px;
  font-weight: 500;
  display: flex; align-items: center; justify-content: flex-start; gap: 6px;
  transition: background .14s ease, border-color .14s ease, transform .12s ease, box-shadow .14s ease;
  text-align: left;
}
.prop-action-btn:hover:not(:disabled) {
  background: linear-gradient(135deg, rgba(170, 110, 250, 0.62) 0%, rgba(255, 110, 220, 0.55) 100%);
  border-color: rgba(255, 130, 220, 0.7);
  color: #fff;
  box-shadow: 0 4px 12px rgba(180, 90, 220, 0.40);
}
.prop-action-btn:active:not(:disabled) { transform: scale(0.985); }
.prop-action-btn:disabled { opacity: 0.6; cursor: progress; }
.prop-action-icon {
  font-size: 9px;
  color: rgba(255, 200, 250, 0.85);
  flex: none;
}
.prop-action-spinner {
  display: inline-block;
  animation: prop-spin .8s linear infinite;
  font-size: 13px;
}
.prop-action-label {
  flex: 1;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

@keyframes prop-spin {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}

.defaults-form .el-button { margin-top: 8px; }
.layer-table { margin: 6px 0; }
.new-layer { margin-top: 8px; }
</style>
