<template>
  <div class="inspector">
    <template v-if="!node || !refKind">
      <el-empty description="点选画布节点，或从左侧拖入积木" :image-size="72" />
    </template>

    <template v-else-if="refKind === 'trigger'">
      <h4>触发</h4>
      <el-form label-position="top" size="small">
        <el-form-item label="类型">
          <el-select v-model="tpl.trigger.type" style="width: 100%">
            <el-option label="事件" value="event" />
            <el-option label="人工" value="manual" />
          </el-select>
        </el-form-item>
        <el-form-item label="事件源">
          <el-input v-model="tpl.trigger.source" placeholder="mes.materialCall" />
        </el-form-item>
        <el-form-item label="优先级">
          <el-input-number v-model="tpl.meta.priority" :min="1" :max="999" style="width: 100%" />
        </el-form-item>
        <el-form-item label="互斥组">
          <el-input v-model="tpl.meta.mutexGroup" placeholder="line-side-replenish" />
        </el-form-item>
      </el-form>
    </template>

    <template v-else-if="refKind === 'binding' && binding">
      <h4>参数绑定</h4>
      <el-form label-position="top" size="small">
        <el-form-item label="写入上下文名 (as)">
          <el-input v-model="binding.as" />
        </el-form-item>
        <el-form-item label="来自字段目录">
          <el-select v-model="binding.from" filterable style="width: 100%" @change="onFromChange">
            <el-option
              v-for="f in paramFields"
              :key="`${f.module}.${f.path}`"
              :label="`${f.module}.${f.path} · ${f.description}`"
              :value="`${f.module}.${f.path}`"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="必填">
          <el-switch v-model="binding.required" />
        </el-form-item>
        <el-form-item v-if="binding.resolve === 'lookup'" label="查询键模板">
          <el-input v-model="lookupTemplate" placeholder="line:{lineId}" @change="syncLookupKey" />
        </el-form-item>
        <el-form-item v-if="binding.resolve === 'lookup'" label="失败策略">
          <el-select v-model="binding.onError" style="width: 100%">
            <el-option label="用缓存" value="cached" />
            <el-option label="挂起" value="suspend" />
            <el-option label="默认值" value="default" />
            <el-option label="失败" value="fail" />
          </el-select>
        </el-form-item>
        <el-button type="danger" plain size="small" @click="removeBinding">删除此绑定</el-button>
      </el-form>
    </template>

    <template v-else-if="refKind === 'filter' && filterGroup">
      <h4>过滤条件 · {{ sideLabel }}</h4>
      <el-form label-position="top" size="small">
        <el-form-item label="组 ID">
          <el-input v-model="filterGroup.id" disabled />
        </el-form-item>
        <el-form-item label="级别">
          <el-radio-group v-model="filterGroup.severity">
            <el-radio value="hard">硬性（不可降级放开）</el-radio>
            <el-radio value="soft">弹性（可降级）</el-radio>
          </el-radio-group>
        </el-form-item>
      </el-form>
      <div class="cmp-title">条件行（组内 AND）</div>
      <div v-for="(row, i) in compareRows" :key="i" class="cmp-row">
        <el-select v-model="row.left" size="small" filterable allow-create style="width: 100%">
          <el-option v-for="f in wcsFields" :key="f" :label="f" :value="f" />
        </el-select>
        <el-select v-model="row.op" size="small" style="width: 100%">
          <el-option v-for="op in ops" :key="op" :label="op" :value="op" />
        </el-select>
        <el-input v-model="row.right" size="small" placeholder="常量或 context.xxx" />
        <el-button size="small" text type="danger" @click="removeCompare(i)">删</el-button>
      </div>
      <el-button size="small" @click="addCompare">加条件</el-button>
      <el-button size="small" type="danger" plain @click="removeFilter">删除此过滤组</el-button>
    </template>

    <template v-else-if="refKind === 'score' && scoreRule">
      <h4>打分 · {{ sideLabel }}</h4>
      <el-form label-position="top" size="small">
        <el-form-item label="函数">
          <el-select v-model="scoreRule.function" style="width: 100%">
            <el-option label="距离更近" value="nearer_to_ref" />
            <el-option label="先入先出" value="fifo_age" />
            <el-option label="字段匹配加分" value="field_match_bonus" />
            <el-option label="固定分" value="constant" />
          </el-select>
        </el-form-item>
        <el-form-item label="权重">
          <el-input-number v-model="scoreRule.weight" :min="0.1" :step="0.5" style="width: 100%" />
        </el-form-item>
        <el-form-item v-if="scoreRule.function === 'nearer_to_ref'" label="参照点 X">
          <el-input-number v-model="(scoreRule.params as any).refX" style="width: 100%" />
        </el-form-item>
        <el-form-item v-if="scoreRule.function === 'nearer_to_ref'" label="参照点 Y">
          <el-input-number v-model="(scoreRule.params as any).refY" style="width: 100%" />
        </el-form-item>
        <el-form-item v-if="scoreRule.function === 'constant'" label="分值">
          <el-input-number v-model="(scoreRule.params as any).value" style="width: 100%" />
        </el-form-item>
        <el-button type="danger" plain size="small" @click="removeScore">删除此打分</el-button>
      </el-form>
    </template>

    <template v-else-if="refKind === 'allocate'">
      <h4>成对预占</h4>
      <el-alert type="info" :closable="false" show-icon class="mb" title="源/宿任一失败则全体回滚并释放预占" />
      <el-form label-position="top" size="small">
        <el-form-item label="源 TopN">
          <el-input-number v-model="tpl.locationStrategies.source.allocate.topN" :min="1" :max="50" style="width: 100%" />
        </el-form-item>
        <el-form-item label="宿 TopN">
          <el-input-number v-model="tpl.locationStrategies.target.allocate.topN" :min="1" :max="50" style="width: 100%" />
        </el-form-item>
      </el-form>
    </template>

    <template v-else-if="refKind === 'action'">
      <h4>任务动作</h4>
      <el-form label-position="top" size="small">
        <el-form-item label="任务类型">
          <el-input model-value="transport" disabled />
        </el-form-item>
        <el-form-item label="自动派车">
          <el-switch v-model="tpl.blueprint.options.autoDispatch" />
        </el-form-item>
        <el-form-item label="优先级">
          <el-input-number v-model="tpl.blueprint.options.priority" :min="1" :max="999" style="width: 100%" />
        </el-form-item>
      </el-form>
    </template>

    <template v-else-if="refKind === 'policy'">
      <h4>运行策略</h4>
      <el-form label-position="top" size="small">
        <el-form-item label="预占超时（秒）">
          <el-input-number v-model="tpl.policy.reservationTtlSec" :min="30" :max="3600" style="width: 100%" />
        </el-form-item>
        <el-form-item label="选位最大尝试">
          <el-input-number v-model="tpl.policy.allocateMaxAttempts" :min="1" :max="20" style="width: 100%" />
        </el-form-item>
        <el-form-item label="无候选时">
          <el-select v-model="tpl.policy.onNoCandidate" style="width: 100%">
            <el-option label="告警" value="raise_alert" />
            <el-option label="失败" value="fail" />
          </el-select>
        </el-form-item>
      </el-form>
    </template>

    <template v-else>
      <el-empty description="此节点无需配置" :image-size="64" />
    </template>
  </div>
</template>

<script setup lang="ts">
import { computed, reactive, watch } from 'vue'
import type { CompareOp, ExprNode, FilterGroup, TaskTemplateDsl } from '../types'
import { PARAM_FIELDS } from '../mock/seed'
import type { WcsFlowNode } from './flowBridge'

const props = defineProps<{
  tpl: TaskTemplateDsl
  node: WcsFlowNode | null
}>()

const emit = defineEmits<{
  change: []
  removeFocus: []
}>()

const paramFields = PARAM_FIELDS
const ops: CompareOp[] = ['eq', 'ne', 'in', 'contains', 'gt', 'gte', 'lt', 'lte', 'exists']
const wcsFields = [
  'wcs.disabled',
  'wcs.storageType',
  'wcs.status',
  'wcs.materialAffinity',
  'wcs.allowInbound',
  'wcs.allowOutbound',
  'wcs.lineId',
  'wcs.areaId',
  'wcs.x',
  'wcs.y'
]

const nodeData = computed(() => props.node?.data)
const refKind = computed(() => nodeData.value?.ref?.kind)
const side = computed(() => nodeData.value?.ref?.side)
const sideLabel = computed(() => (side.value === 'target' ? '宿' : '源'))

const binding = computed(() => {
  const idx = nodeData.value?.ref?.index
  if (idx == null || idx < 0) return null
  return props.tpl.bindings[idx] ?? null
})

const filterGroup = computed(() => {
  const r = nodeData.value?.ref
  if (r?.kind !== 'filter' || !r.side || !r.id) return null
  return props.tpl.locationStrategies[r.side].filterGroups.find((g) => g.id === r.id) ?? null
})

const scoreRule = computed(() => {
  const r = nodeData.value?.ref
  if (r?.kind !== 'score' || !r.side || !r.id) return null
  return props.tpl.locationStrategies[r.side].scores.find((s) => s.id === r.id) ?? null
})

watch(
  scoreRule,
  (s) => {
    if (!s) return
    if (s.function === 'nearer_to_ref') {
      if (s.params.refX == null) s.params.refX = 50
      if (s.params.refY == null) s.params.refY = 10
    }
    if (s.function === 'constant' && s.params.value == null) s.params.value = 50
  },
  { immediate: true }
)

type CmpRow = { left: string; op: CompareOp; right: string }
const compareRows = reactive<CmpRow[]>([])

function parseRight(raw: string): { ref?: string; const?: unknown } {
  const t = raw.trim()
  if (t.startsWith('context.') || t.startsWith('wcs.')) return { ref: t }
  if (t === 'true') return { const: true }
  if (t === 'false') return { const: false }
  if (/^-?\d+(\.\d+)?$/.test(t)) return { const: Number(t) }
  if (t.startsWith('[') && t.endsWith(']')) {
    try {
      return { const: JSON.parse(t) }
    } catch {
      /* fallthrough */
    }
  }
  return { const: t }
}

function syncExprFromRows(g: FilterGroup) {
  g.expr = {
    type: 'group',
    op: 'and',
    children: compareRows.map((row) => {
      const right = parseRight(row.right)
      const node: ExprNode = {
        type: 'compare',
        left: { ref: row.left },
        op: row.op,
        right: 'ref' in right && right.ref ? { ref: right.ref } : { const: right.const as never }
      }
      return node
    })
  }
  emit('change')
}

function loadCompareRows(g: FilterGroup | null) {
  compareRows.splice(0, compareRows.length)
  if (!g || g.expr.type !== 'group') return
  for (const c of g.expr.children) {
    if (c.type !== 'compare') continue
    const left = 'ref' in c.left ? c.left.ref : ''
    let right = ''
    if (c.right) {
      if ('ref' in c.right) right = c.right.ref
      else right = typeof c.right.const === 'string' ? c.right.const : JSON.stringify(c.right.const)
    }
    compareRows.push({ left, op: c.op, right })
  }
}

watch(
  () => filterGroup.value,
  (g) => loadCompareRows(g),
  { immediate: true }
)

watch(
  compareRows,
  () => {
    if (filterGroup.value) syncExprFromRows(filterGroup.value)
  },
  { deep: true }
)

function addCompare() {
  compareRows.push({ left: 'wcs.status', op: 'eq', right: 'EmptyContainer' })
}
function removeCompare(i: number) {
  compareRows.splice(i, 1)
}

const lookupTemplate = computed({
  get: () => binding.value?.key?.template ?? '',
  set: (v: string) => {
    if (!binding.value) return
    if (!binding.value.key) binding.value.key = { template: v, args: { lineId: 'context.lineId' } }
    else binding.value.key.template = v
    emit('change')
  }
})

function syncLookupKey() {
  emit('change')
}

function onFromChange(from: string) {
  const b = binding.value
  if (!b) return
  const def = PARAM_FIELDS.find((f) => `${f.module}.${f.path}` === from)
  if (def) {
    b.resolve = def.resolveMode
    if (def.resolveMode === 'lookup' && !b.key) {
      b.key = { template: 'line:{lineId}', args: { lineId: 'context.lineId' } }
      b.onError = b.onError ?? 'default'
    }
  }
  emit('change')
}

function removeBinding() {
  const idx = props.node?.data?.ref?.index
  if (idx == null || idx < 0) return
  props.tpl.bindings.splice(idx, 1)
  emit('removeFocus')
  emit('change')
}

function removeFilter() {
  const r = props.node?.data?.ref
  if (r?.kind !== 'filter' || !r.side || !r.id) return
  const st = props.tpl.locationStrategies[r.side]
  st.filterGroups = st.filterGroups.filter((g) => g.id !== r.id)
  for (const step of st.degradeChain) {
    step.requireGroups = step.requireGroups.filter((id) => id !== r.id)
    step.drop = (step.drop ?? []).filter((id) => id !== r.id)
  }
  emit('removeFocus')
  emit('change')
}

function removeScore() {
  const r = props.node?.data?.ref
  if (r?.kind !== 'score' || !r.side || !r.id) return
  const st = props.tpl.locationStrategies[r.side]
  st.scores = st.scores.filter((s) => s.id !== r.id)
  emit('removeFocus')
  emit('change')
}

// touch emit on simple field edits via watchers on tpl is hard; inspector mutates tpl in place
watch(
  () => props.tpl,
  () => emit('change'),
  { deep: true }
)
</script>

<style scoped>
.inspector {
  padding: 10px 12px 20px;
  overflow: auto;
  height: 100%;
  box-sizing: border-box;
}
h4 {
  margin: 0 0 10px;
  font-size: 14px;
}
.cmp-title {
  font-size: 12px;
  font-weight: 600;
  margin: 8px 0 6px;
}
.cmp-row {
  display: grid;
  grid-template-columns: 1fr 90px 1fr auto;
  gap: 4px;
  margin-bottom: 6px;
  align-items: center;
}
.mb {
  margin-bottom: 10px;
}
</style>
