/**
 * 规范化 JSON 序列化 —— **递归按键名排序**后再 stringify。
 *
 * ⚠️ 凡是「把库里读出来的 jsonb 和代码里新算出来的对象比一比,判断变没变」的场景都必须用它:
 * Postgres `jsonb` **不保留键序**(按键长度 + 字节序重排存储),而代码构造对象的顺序是源码顺序。
 * 直接 `JSON.stringify` 比较,两边键序必然不同 → 每次都判"变了" → 每轮重算全量重写。
 * (plan_reasons 实测:不加这层,连跑三次每次都刷新。)
 *
 * 现有消费方:
 *   - plan/engine/reason-refresh.ts   召回原因就地刷新判定
 *   - persona/persona-diff.ts         画像写入判定
 */
export function canonicalJson(v: unknown): string {
  return JSON.stringify(normalize(v));
}

/** 递归:数组保序(顺序有语义),对象按键名排序,undefined 值丢弃(对齐 JSON.stringify 语义)。 */
function normalize(x: unknown): unknown {
  if (Array.isArray(x)) return x.map(normalize);
  if (x !== null && typeof x === 'object') {
    const o = x as Record<string, unknown>;
    return Object.keys(o)
      .sort()
      .reduce<Record<string, unknown>>((acc, k) => {
        if (o[k] !== undefined) acc[k] = normalize(o[k]);
        return acc;
      }, {});
  }
  return x;
}
