import type { DeriveExpr, DeriveOp } from '../transforms.schema';
import { isEmptyText, type Row } from '../row';

/**
 * derive — 给行加新字段(in-place,output 可以同 input)。
 *
 * 7 个内置 op(纯函数,无副作用):
 *   - substring   { op, from, start, end? }
 *   - concat      { op, parts: ["${field}" | "literal", ...] }
 *   - lower / upper  { op, from }
 *   - default     { op, from, value } — 原值空时填 value
 *   - trim        { op, from } — 去首尾空白 + 尾部噪声标点(脏中文名归一)
 *   - coalesce    { op, from: [..] } — 取第一个非空字段值
 *
 * 不允许任意 JS 表达式 / eval。撞墙必须加新 op + 走 review。
 */
/// 尾部噪声标点(中英文)+ 空白;诊断 message 归一用(牙龈炎。/牙龈炎；/牙龈炎\r\n → 牙龈炎)
const TRAILING_NOISE = /[\s。;；,，?？!！、.]+$/u;

export function runDerive(op: DeriveOp, rows: Row[]): Row[] {
  return rows.map((row) => {
    const next: Row = { ...row };
    for (const [outputField, expr] of Object.entries(op.fields)) {
      next[outputField] = evalExpr(expr, next);
    }
    return next;
  });
}

function evalExpr(expr: DeriveExpr, row: Row): unknown {
  switch (expr.op) {
    case 'substring': {
      const v = row[expr.from];
      if (typeof v !== 'string') return v ?? null;
      return expr.end !== undefined ? v.substring(expr.start, expr.end) : v.substring(expr.start);
    }
    case 'concat': {
      const parts = expr.parts.map((p) => {
        const m = /^\$\{([a-zA-Z_][a-zA-Z0-9_]*)\}$/.exec(p);
        if (m) {
          const val = row[m[1]!];
          return val === undefined || val === null ? '' : String(val);
        }
        return p;
      });
      return parts.join('');
    }
    case 'lower': {
      const v = row[expr.from];
      return typeof v === 'string' ? v.toLowerCase() : (v ?? null);
    }
    case 'upper': {
      const v = row[expr.from];
      return typeof v === 'string' ? v.toUpperCase() : (v ?? null);
    }
    case 'default': {
      const v = row[expr.from];
      return isEmptyText(v) ? expr.value : v;
    }
    case 'trim': {
      const v = row[expr.from];
      if (typeof v !== 'string') return v ?? null;
      return v.trim().replace(TRAILING_NOISE, '').trim();
    }
    case 'coalesce': {
      for (const f of expr.from) {
        const v = row[f];
        if (!isEmptyText(v)) return v;
      }
      return null;
    }
  }
}
