import type { LookupOp, LookupWhereRule } from '../transforms.schema';
import { SOURCE_ROW_KEY, isEmptyText, type Row } from '../row';

/**
 * lookup — 按外键 join `from` 子表,挑一条,把 select 字段附加到主表(1:N → 标量)。
 *
 * 左连接语义:输出行数 = input 行数;未命中的行 select 目标字段填 null(保证列存在,
 * 下游 field_mapping 稳定)。多条候选按 order_by 稳定排序取第一条;不配则取首个遇到。
 *
 * __source_row 透传主表自身(不合并 from 的),合规留底不变。
 */
export function runLookup(
  op: LookupOp,
  input: Row[],
  from: Row[],
): { rows: Row[]; matched: number; missed: number } {
  // 1. 建 from 索引(right_key → 候选行);建索引时先跑 where 预过滤
  const index = new Map<string, Row[]>();
  for (const r of from) {
    if (op.where && !passLookupWhere(r, op.where)) continue;
    const k = keyOf(r[op.right_key]);
    if (k === null) continue;
    const bucket = index.get(k);
    if (bucket) bucket.push(r);
    else index.set(k, [r]);
  }

  // 2. 逐主表行取候选、挑一条、附加字段
  let matched = 0;
  let missed = 0;
  const rows = input.map((row) => {
    const out: Row = { ...row };
    const k = keyOf(row[op.left_key]);
    const candidates = k === null ? undefined : index.get(k);
    if (candidates && candidates.length > 0) {
      const chosen = pickOne(candidates, op.order_by);
      for (const [target, fromField] of Object.entries(op.select)) {
        out[target] = chosen[fromField] ?? null;
      }
      matched++;
    } else {
      // 未命中:填 null(仅当主表尚无该列,不覆盖已有值)
      for (const target of Object.keys(op.select)) {
        if (!(target in out)) out[target] = null;
      }
      missed++;
    }
    // __source_row 保留主表自身(spread 已带,显式兜底一次防被 select 覆盖同名 key)
    if (SOURCE_ROW_KEY in row) out[SOURCE_ROW_KEY] = row[SOURCE_ROW_KEY];
    return out;
  });

  return { rows, matched, missed };
}

/// join 键归一:null/undefined/空串 → null(不参与匹配);其余 String 化对齐(CSV 全是字符串)
function keyOf(v: unknown): string | null {
  if (v === undefined || v === null) return null;
  const s = String(v).trim();
  return s === '' ? null : s;
}

/// 候选排序取第一条(order_by 为空 → 首个遇到)。稳定:多键顺序裁决,数值优先按数值比。
function pickOne(cands: Row[], orderBy?: LookupOp['order_by']): Row {
  if (!orderBy || orderBy.length === 0) return cands[0]!;
  const sorted = [...cands].sort((a, b) => {
    for (const { field, dir } of orderBy) {
      const cmp = compareVals(a[field], b[field]);
      if (cmp !== 0) return dir === 'desc' ? -cmp : cmp;
    }
    return 0;
  });
  return sorted[0]!;
}

function toNum(v: unknown): number | null {
  if (typeof v === 'number') return Number.isFinite(v) ? v : null;
  if (typeof v === 'string' && v.trim() !== '' && !Number.isNaN(Number(v))) return Number(v);
  return null;
}

/// 比较:两侧都能当数字 → 数值比;否则字符串比。null/空当最小(排后需配 dir)。
function compareVals(a: unknown, b: unknown): number {
  const na = toNum(a);
  const nb = toNum(b);
  if (na !== null && nb !== null) return na - nb;
  const sa = a === null || a === undefined ? '' : String(a);
  const sb = b === null || b === undefined ? '' : String(b);
  return sa < sb ? -1 : sa > sb ? 1 : 0;
}

/// 候选行预过滤(AND;标量子集:not_empty / empty / in / equals)
function passLookupWhere(row: Row, where: Record<string, LookupWhereRule>): boolean {
  for (const [field, rule] of Object.entries(where)) {
    const v = row[field];
    if ('not_empty' in rule) {
      if (isEmptyText(v)) return false;
    } else if ('empty' in rule) {
      if (!isEmptyText(v)) return false;
    } else if ('in' in rule) {
      const set = new Set(rule.in.map((x) => String(x)));
      if (!set.has(String(v))) return false;
    } else if ('equals' in rule) {
      if (String(v) !== String(rule.equals)) return false;
    }
  }
  return true;
}
