import type { CompareOp, ExprNode, StorageLoc, ValueRef } from '../types'

function readPath(obj: Record<string, unknown>, path: string): unknown {
  const parts = path.split('.')
  let cur: unknown = obj
  for (const p of parts) {
    if (cur == null || typeof cur !== 'object') return undefined
    cur = (cur as Record<string, unknown>)[p]
  }
  return cur
}

export function locToWcs(loc: StorageLoc): Record<string, unknown> {
  return {
    storageId: loc.storageId,
    areaId: loc.areaId,
    areaType: loc.areaType,
    storageType: loc.storageType,
    status: loc.status,
    materialAffinity: loc.materialAffinity,
    disabled: loc.disabled,
    lineId: loc.lineId,
    allowInbound: loc.allowInbound,
    allowOutbound: loc.allowOutbound,
    containerType: loc.containerType,
    batchNo: loc.batchNo,
    qty: loc.qty,
    inboundAt: loc.inboundAt,
    x: loc.x,
    y: loc.y,
    reserved: !!loc.forceReserved
  }
}

function resolveValue(
  side: ValueRef | undefined,
  wcs: Record<string, unknown>,
  context: Record<string, unknown>
): unknown {
  if (!side) return undefined
  if ('const' in side) return side.const
  const path = side.ref
  if (path.startsWith('wcs.')) return readPath({ wcs }, path) ?? readPath(wcs, path.slice(4))
  if (path.startsWith('context.')) return readPath({ context }, path) ?? readPath(context, path.slice(8))
  return undefined
}

function isEmpty(v: unknown): boolean {
  return v === undefined || v === null || v === ''
}

export interface CompareFail {
  reason: string
  op: CompareOp
}

export function evalCompare(
  node: Extract<ExprNode, { type: 'compare' }>,
  wcs: Record<string, unknown>,
  context: Record<string, unknown>
): { ok: boolean; fail?: CompareFail } {
  const left = resolveValue(node.left, wcs, context)
  const right = resolveValue(node.right, wcs, context)
  const op = node.op

  if (op === 'exists') return { ok: !isEmpty(left) }
  if (op === 'notExists') return { ok: isEmpty(left) }

  if (isEmpty(left) || (node.right && isEmpty(right) && op !== 'eq')) {
    // eq with explicit null const still allowed; otherwise null → false
    if (!(op === 'eq' && node.right && 'const' in node.right && node.right.const === null)) {
      return { ok: false, fail: { reason: 'null_operand', op } }
    }
  }

  switch (op) {
    case 'eq': return { ok: left === right, fail: left === right ? undefined : { reason: `${fmt(left)} ≠ ${fmt(right)}`, op } }
    case 'ne': return { ok: left !== right }
    case 'in': {
      const arr = Array.isArray(right) ? right : []
      const ok = arr.includes(left as never)
      return { ok, fail: ok ? undefined : { reason: `${fmt(left)} not in ${fmt(arr)}`, op } }
    }
    case 'notIn': {
      const arr = Array.isArray(right) ? right : []
      return { ok: !arr.includes(left as never) }
    }
    case 'contains': {
      const arr = Array.isArray(left) ? left : []
      const ok = arr.includes(right as never)
      return { ok, fail: ok ? undefined : { reason: `${fmt(arr)} 不含 ${fmt(right)}`, op } }
    }
    case 'notContains': {
      const arr = Array.isArray(left) ? left : []
      return { ok: !arr.includes(right as never) }
    }
    case 'gt': return { ok: Number(left) > Number(right) }
    case 'gte': return { ok: Number(left) >= Number(right) }
    case 'lt': return { ok: Number(left) < Number(right) }
    case 'lte': return { ok: Number(left) <= Number(right) }
    case 'between': {
      const arr = Array.isArray(right) ? right : []
      const n = Number(left)
      return { ok: n >= Number(arr[0]) && n <= Number(arr[1]) }
    }
    case 'matchesRef': return { ok: left === right }
    default: return { ok: false, fail: { reason: `unknown_op:${op}`, op } }
  }
}

function fmt(v: unknown): string {
  try { return JSON.stringify(v) } catch { return String(v) }
}

export function evalExpr(
  node: ExprNode,
  wcs: Record<string, unknown>,
  context: Record<string, unknown>
): { ok: boolean; failReason?: string } {
  if (node.type === 'compare') {
    const r = evalCompare(node, wcs, context)
    return { ok: r.ok, failReason: r.fail?.reason }
  }
  if (node.op === 'and') {
    for (const c of node.children) {
      const r = evalExpr(c, wcs, context)
      if (!r.ok) return r
    }
    return { ok: true }
  }
  for (const c of node.children) {
    const r = evalExpr(c, wcs, context)
    if (r.ok) return { ok: true }
  }
  return { ok: false, failReason: 'or_group_all_failed' }
}
