import type { ParamBinding, ParamFieldDef } from '../types'
import { LOOKUP_TABLE, PARAM_FIELDS } from '../mock/seed'

export interface AssembleResult {
  ok: boolean
  context: Record<string, unknown>
  suspend?: boolean
  error?: string
  explain: Array<{ as: string; from: string; value?: unknown; error?: string }>
}

const cache = new Map<string, { value: unknown; expireAt: number }>()

export function clearLookupCache() {
  cache.clear()
}

function payloadKey(from: string): string {
  // mes.call.materialCode → materialCode (last segment) also try full after module
  const parts = from.split('.')
  if (parts.length >= 2) return parts[parts.length - 1]
  return from
}

function buildLookupKey(template: string, args: Record<string, string>, context: Record<string, unknown>): string {
  return template.replace(/\{(\w+)\}/g, (_, name: string) => {
    const path = args[name]
    if (!path) return ''
    const ctxPath = path.startsWith('context.') ? path.slice(8) : path
    return String(context[ctxPath] ?? '')
  })
}

export function assembleContext(
  bindings: ParamBinding[],
  eventPayload: Record<string, unknown>,
  options?: { lookupBroken?: boolean; fields?: ParamFieldDef[] }
): AssembleResult {
  const fields = options?.fields ?? PARAM_FIELDS
  const context: Record<string, unknown> = {}
  const explain: AssembleResult['explain'] = []
  const byFrom = new Map(fields.map((f) => [`${f.module}.${f.path}`, f]))

  const ordered = [...bindings].sort((a, b) => {
    const ra = a.resolve ?? byFrom.get(a.from)?.resolveMode ?? 'eventPayload'
    const rb = b.resolve ?? byFrom.get(b.from)?.resolveMode ?? 'eventPayload'
    const rank = (r: string) => (r === 'eventPayload' ? 0 : r === 'session' ? 1 : 2)
    return rank(ra) - rank(rb)
  })

  for (const b of ordered) {
    const def = byFrom.get(b.from)
    const mode = b.resolve ?? def?.resolveMode ?? 'eventPayload'
    try {
      if (mode === 'eventPayload' || mode === 'session') {
        const key = payloadKey(b.from)
        let value = eventPayload[key]
        if (value === undefined && b.from.includes('.')) {
          // also allow nested payload.mes.call.materialCode style
          value = eventPayload[b.from]
        }
        if (value === undefined) value = b.default
        if (value === undefined && b.required) {
          explain.push({ as: b.as, from: b.from, error: 'required_missing' })
          return { ok: false, context, error: `缺少必填参数 ${b.as}`, explain }
        }
        context[b.as] = value
        explain.push({ as: b.as, from: b.from, value })
        continue
      }

      // lookup
      if (options?.lookupBroken) {
        const onError = b.onError ?? 'fail'
        if (onError === 'cached') {
          const ck = b.key ? buildLookupKey(b.key.template, b.key.args, context) : b.from
          const hit = cache.get(ck)
          if (hit && hit.expireAt > Date.now()) {
            context[b.as] = hit.value
            explain.push({ as: b.as, from: b.from, value: hit.value, error: 'used_cache_after_error' })
            continue
          }
        }
        if (onError === 'default') {
          context[b.as] = b.default
          explain.push({ as: b.as, from: b.from, value: b.default, error: 'lookup_broken_default' })
          continue
        }
        if (onError === 'suspend') {
          explain.push({ as: b.as, from: b.from, error: 'lookup_suspend' })
          return { ok: false, context, suspend: true, error: 'lookup 暂不可用', explain }
        }
        explain.push({ as: b.as, from: b.from, error: 'lookup_fail' })
        return { ok: false, context, error: `lookup 失败: ${b.as}`, explain }
      }

      if (!b.key) {
        explain.push({ as: b.as, from: b.from, error: 'lookup_key_missing' })
        return { ok: false, context, error: `lookup 缺少 key: ${b.as}`, explain }
      }
      const lk = buildLookupKey(b.key.template, b.key.args, context)
      const value = LOOKUP_TABLE[lk] ?? b.default
      if (value === undefined && b.required) {
        explain.push({ as: b.as, from: b.from, error: 'lookup_miss' })
        return { ok: false, context, error: `lookup 无结果: ${b.as}`, explain }
      }
      context[b.as] = value
      const ttl = (b.cacheTtlSec ?? 60) * 1000
      cache.set(lk, { value, expireAt: Date.now() + ttl })
      explain.push({ as: b.as, from: b.from, value })
    } catch (e) {
      explain.push({ as: b.as, from: b.from, error: String(e) })
      return { ok: false, context, error: String(e), explain }
    }
  }

  return { ok: true, context, explain }
}
