import type { TaskTemplateDsl } from '../types'
import { PARAM_FIELDS } from '../mock/seed'

export interface ValidationIssue {
  code: string
  level: 'error' | 'warn'
  message: string
  path?: string
}

export function validateTemplate(tpl: TaskTemplateDsl, mode: 'save' | 'publish'): ValidationIssue[] {
  const issues: ValidationIssue[] = []
  const err = (code: string, message: string, path?: string) =>
    issues.push({ code, level: 'error', message, path })
  const warn = (code: string, message: string, path?: string) =>
    issues.push({ code, level: 'warn', message, path })

  if (tpl.schemaVersion !== 1) err('S01', 'schemaVersion 必须为 1')
  if (!tpl.name?.trim()) err('S02', '模板名不能为空')
  if (typeof tpl.meta?.priority !== 'number') err('S02', 'priority 必须为数字', 'meta.priority')
  if (!tpl.locationStrategies?.source || !tpl.locationStrategies?.target) {
    err('S03', '必须配置 source 与 target 策略')
  }

  const asSet = new Set<string>()
  for (const b of tpl.bindings ?? []) {
    if (asSet.has(b.as)) err('S04', `绑定名重复: ${b.as}`, 'bindings')
    asSet.add(b.as)
    if (!PARAM_FIELDS.some((f) => `${f.module}.${f.path}` === b.from)) {
      err('S05', `未知参数字段: ${b.from}`, `bindings.${b.as}`)
    }
    if ((b.resolve === 'lookup' || PARAM_FIELDS.find((f) => `${f.module}.${f.path}` === b.from)?.resolveMode === 'lookup') && !b.key) {
      err('S06', `lookup 绑定缺少 key: ${b.as}`, `bindings.${b.as}`)
    }
  }

  for (const side of ['source', 'target'] as const) {
    const st = tpl.locationStrategies[side]
    if (!st) continue
    const ids = new Set<string>()
    for (const g of st.filterGroups) {
      if (ids.has(g.id)) err('S07', `过滤组 id 重复: ${g.id}`, `${side}.filterGroups`)
      ids.add(g.id)
      if (g.severity !== 'hard' && g.severity !== 'soft') err('S07', `非法 severity: ${g.severity}`)
    }
    for (const s of st.scores) {
      if (!['nearer_to_ref', 'fifo_age', 'field_match_bonus', 'constant'].includes(s.function)) {
        err('S09', `未知打分函数: ${s.function}`, `${side}.scores`)
      }
      if (!(s.weight > 0)) err('S09', `weight 必须 > 0: ${s.id}`, `${side}.scores`)
    }
    const topN = st.allocate?.topN ?? 5
    if (topN < 1 || topN > 100) err('S10', 'topN 应在 1～100', `${side}.allocate.topN`)

    if (mode === 'publish') {
      const hard = st.filterGroups.filter((g) => g.severity === 'hard')
      if (side === 'source' && !hard.length) err('P04', '源侧至少要有一个 hard 过滤组', `${side}`)
      for (const g of st.filterGroups) {
        if (!g.expr || g.expr.type !== 'group') err('P03', `过滤组 ${g.id} 根节点必须是 group`, `${side}.${g.id}`)
      }
      const chain = st.degradeChain ?? []
      for (const step of chain) {
        for (const h of hard) {
          if (!step.requireGroups.includes(h.id)) {
            err('P05', `降级档 ${step.attempt} 未覆盖 hard 组 ${h.id}`, `${side}.degradeChain`)
          }
        }
        for (const d of step.drop ?? []) {
          if (hard.some((h) => h.id === d)) err('P05', `不能 drop hard 组 ${d}`, `${side}.degradeChain`)
        }
      }
      if (!st.scores.length) warn('W02', `${side} 未配置打分，将按库位 ID 排序`, side)
    }
  }

  if (mode === 'publish') {
    if (!tpl.trigger?.source) err('P01', 'trigger.source 必填')
    if (tpl.blueprint?.slots?.from !== 'source' || tpl.blueprint?.slots?.to !== 'target') {
      err('P07', 'blueprint.slots 必须指向 source/target')
    }
    const ttl = tpl.policy?.reservationTtlSec ?? 0
    if (ttl < 30 || ttl > 3600) err('P08', 'reservationTtlSec 应在 30～3600')
  }

  return issues
}
