/**
 * Transform pipeline 的行类型 — 纯 dict,运行时校验由各 operator 自负。
 *
 * row[field] 可能是任何类型:string / number / boolean / null / Array / object(JSON 嵌套)
 *
 * **特殊字段 `__source_row`**(W3 末加,合规留底):
 *   TransformEngine 入口给每个源行打上 `__source_row = 原 host row(完整对象)`,
 *   所有 operator(project / split / derive / route / pick / filter)透传不动它。
 *   AssemblerEngine 在 assemble 时提取出来作为 `rawSource`,落到 `patient_transactions.raw_payload` —
 *   保证 raw_payload **= host 真原文**(包括 EMR.diag / treat_plan / file_url 整体 JSON),
 *   即便 fact 是从 EMR 拆出来的(diagnosis / treatment / recommendation),
 *   仍能反查到该 fact 的源 EMR 全貌。
 */
export type Row = Record<string, unknown>;

/// 真原文 link 字段名 — 所有 operator 必须透传,AssemblerEngine 在出口提取
export const SOURCE_ROW_KEY = '__source_row';

/**
 * 表字典 — operator input/output 都在这个 namespace 里查找/写入。
 */
export type TableMap = Record<string, Row[]>;

/**
 * 字段取值兜底:undefined/null 都视为"空",其他原样返回。
 */
export function fieldValue(row: Row, key: string): unknown {
  const v = row[key];
  if (v === undefined || v === null) return null;
  return v;
}

/**
 * 字符串化 + 空判定:undefined/null/空串都视为空。
 */
export function isEmptyText(v: unknown): boolean {
  return v === undefined || v === null || (typeof v === 'string' && v.trim() === '');
}

/**
 * 把可能是 stringified JSON 的字段尝试 parse 为 array;parse 失败或本身不是 array 返回 null。
 */
export function tryParseJsonArray(v: unknown): unknown[] | null {
  if (Array.isArray(v)) return v;
  if (typeof v !== 'string') return null;
  const trimmed = v.trim();
  if (!trimmed || trimmed === '[]') return [];
  if (!trimmed.startsWith('[')) return null;
  try {
    const parsed = JSON.parse(trimmed);
    return Array.isArray(parsed) ? parsed : null;
  } catch {
    return null;
  }
}
