/**
 * 判定规则引擎
 * <p>
 * 递归下降解析成 AST 后自己求值，全程不碰 eval / new Function：
 * 规则文本来自用户，一旦能被当作代码执行就是注入口子。
 * 支持的语法见 docs/智能质检报告平台-方案设计.md §13。
 */
import { parsePath, readPath, toNumber, toNumberArray } from './path';

/** 规则文本存在语法错误 */
export class RuleSyntaxError extends Error {
  constructor(
    message: string,
    readonly position: number,
  ) {
    super(message);
    this.name = 'RuleSyntaxError';
  }
}

/** 规则运行期错误（函数不存在、参数类型不对等） */
export class RuleRuntimeError extends Error {
  constructor(message: string) {
    super(message);
    this.name = 'RuleRuntimeError';
  }
}

type TokenType = 'comma' | 'eof' | 'lparen' | 'number' | 'operator' | 'rparen' | 'string' | 'word';

interface Token {
  type: TokenType;
  value: string;
  position: number;
}

export type RuleNode =
  | { type: 'literal'; value: boolean | null | number | string }
  | { type: 'path'; path: string }
  | { type: 'call'; name: string; args: RuleNode[] }
  | { type: 'unary'; operator: string; operand: RuleNode }
  | { type: 'binary'; operator: string; left: RuleNode; right: RuleNode }
  | { type: 'between'; value: RuleNode; lower: RuleNode; upper: RuleNode; negated: boolean }
  | { type: 'in'; value: RuleNode; items: RuleNode[]; negated: boolean };

const KEYWORDS = new Set([
  'AND',
  'BETWEEN',
  'FALSE',
  'IN',
  'NOT',
  'NOT_IN',
  'NULL',
  'OR',
  'TRUE',
]);

const IDENT_START = /[A-Za-z_$]/;
const IDENT_PART = /[\w$]/;
const TWO_CHAR_OPERATORS = new Set(['<=', '>=', '!=', '<>', '==']);

/* ------------------------------ 词法分析 ------------------------------ */

function tokenize(source: string): Token[] {
  const tokens: Token[] = [];
  const length = source.length;
  let index = 0;

  while (index < length) {
    const char = source[index]!;

    if (/\s/.test(char)) {
      index += 1;
      continue;
    }

    if (char === "'" || char === '"') {
      const quote = char;
      let value = '';
      let cursor = index + 1;
      while (cursor < length && source[cursor] !== quote) {
        if (source[cursor] === '\\' && cursor + 1 < length) {
          value += source[cursor + 1];
          cursor += 2;
          continue;
        }
        value += source[cursor];
        cursor += 1;
      }
      if (cursor >= length) {
        throw new RuleSyntaxError('字符串缺少结尾引号', index);
      }
      tokens.push({ type: 'string', value, position: index });
      index = cursor + 1;
      continue;
    }

    if (/[0-9]/.test(char)) {
      let cursor = index;
      while (cursor < length && /[0-9.]/.test(source[cursor]!)) {
        cursor += 1;
      }
      const text = source.slice(index, cursor);
      if (!/^\d+(?:\.\d+)?$/.test(text)) {
        throw new RuleSyntaxError(`数字格式不正确：${text}`, index);
      }
      tokens.push({ type: 'number', value: text, position: index });
      index = cursor;
      continue;
    }

    if (IDENT_START.test(char)) {
      let cursor = index;
      while (cursor < length && IDENT_PART.test(source[cursor]!)) {
        cursor += 1;
      }
      let value = source.slice(index, cursor);
      // 路径尾巴：.属性 与 [下标]
      for (;;) {
        const next = source[cursor];
        if (next === '.' && IDENT_START.test(source[cursor + 1] ?? '')) {
          let end = cursor + 1;
          while (end < length && IDENT_PART.test(source[end]!)) {
            end += 1;
          }
          value += source.slice(cursor, end);
          cursor = end;
          continue;
        }
        if (next === '[') {
          const close = source.indexOf(']', cursor);
          if (close === -1) {
            throw new RuleSyntaxError('数组下标缺少 ]', cursor);
          }
          const inner = source.slice(cursor + 1, close).trim();
          if (!/^\d+$/.test(inner)) {
            throw new RuleSyntaxError(`数组下标只能是数字：${inner}`, cursor);
          }
          value += `[${inner}]`;
          cursor = close + 1;
          continue;
        }
        break;
      }
      tokens.push({ type: 'word', value, position: index });
      index = cursor;
      continue;
    }

    const two = source.slice(index, index + 2);
    if (TWO_CHAR_OPERATORS.has(two)) {
      tokens.push({ type: 'operator', value: two, position: index });
      index += 2;
      continue;
    }

    if (char === '(' || char === ')') {
      tokens.push({ type: char === '(' ? 'lparen' : 'rparen', value: char, position: index });
      index += 1;
      continue;
    }
    if (char === ',') {
      tokens.push({ type: 'comma', value: char, position: index });
      index += 1;
      continue;
    }
    if ('><=+-*/!'.includes(char)) {
      tokens.push({ type: 'operator', value: char, position: index });
      index += 1;
      continue;
    }

    throw new RuleSyntaxError(`无法识别的字符「${char}」`, index);
  }

  tokens.push({ type: 'eof', value: '', position: length });
  return tokens;
}

/* ------------------------------ 语法分析 ------------------------------ */

class Parser {
  private cursor = 0;

  constructor(private readonly tokens: Token[]) {}

  parse(): RuleNode {
    const node = this.parseOr();
    const token = this.peek();
    if (token.type !== 'eof') {
      throw new RuleSyntaxError(`多余的内容：${token.value}`, token.position);
    }
    return node;
  }

  private parseOr(): RuleNode {
    let left = this.parseAnd();
    while (this.matchWord('OR')) {
      left = { type: 'binary', operator: 'OR', left, right: this.parseAnd() };
    }
    return left;
  }

  private parseAnd(): RuleNode {
    let left = this.parseComparison();
    while (this.matchWord('AND')) {
      left = { type: 'binary', operator: 'AND', left, right: this.parseComparison() };
    }
    return left;
  }

  private parseComparison(): RuleNode {
    const left = this.parseAdditive();

    if (this.matchWord('BETWEEN')) {
      const lower = this.parseAdditive();
      if (!this.matchWord('AND')) {
        throw new RuleSyntaxError('BETWEEN 缺少 AND 与上界', this.peek().position);
      }
      const upper = this.parseAdditive();
      return { type: 'between', value: left, lower, upper, negated: false };
    }

    if (this.matchWord('NOT')) {
      if (this.matchWord('IN') || this.matchWord('NOT_IN')) {
        return { type: 'in', value: left, items: this.parseList(), negated: true };
      }
      throw new RuleSyntaxError('NOT 只能用于 NOT IN', this.peek().position);
    }

    if (this.matchWord('IN')) {
      return { type: 'in', value: left, items: this.parseList(), negated: false };
    }

    const token = this.peek();
    if (token.type === 'operator' && ['<', '<=', '=', '==', '!=', '<>', '>', '>='].includes(token.value)) {
      this.cursor += 1;
      const operator = token.value === '==' ? '=' : token.value === '<>' ? '!=' : token.value;
      return { type: 'binary', operator, left, right: this.parseAdditive() };
    }

    return left;
  }

  private parseList(): RuleNode[] {
    this.expect('lparen');
    const items: RuleNode[] = [];
    if (this.peek().type !== 'rparen') {
      items.push(this.parseOr());
      while (this.peek().type === 'comma') {
        this.cursor += 1;
        items.push(this.parseOr());
      }
    }
    this.expect('rparen');
    return items;
  }

  private parseAdditive(): RuleNode {
    let left = this.parseMultiplicative();
    for (;;) {
      const token = this.peek();
      if (token.type !== 'operator' || (token.value !== '+' && token.value !== '-')) {
        return left;
      }
      this.cursor += 1;
      left = { type: 'binary', operator: token.value, left, right: this.parseMultiplicative() };
    }
  }

  private parseMultiplicative(): RuleNode {
    let left = this.parseUnary();
    for (;;) {
      const token = this.peek();
      if (token.type !== 'operator' || (token.value !== '*' && token.value !== '/')) {
        return left;
      }
      this.cursor += 1;
      left = { type: 'binary', operator: token.value, left, right: this.parseUnary() };
    }
  }

  private parseUnary(): RuleNode {
    const token = this.peek();
    if (token.type === 'operator' && (token.value === '-' || token.value === '!')) {
      this.cursor += 1;
      return { type: 'unary', operator: token.value, operand: this.parseUnary() };
    }
    return this.parsePrimary();
  }

  private parsePrimary(): RuleNode {
    const token = this.peek();

    if (token.type === 'number') {
      this.cursor += 1;
      return { type: 'literal', value: Number(token.value) };
    }
    if (token.type === 'string') {
      this.cursor += 1;
      return { type: 'literal', value: token.value };
    }
    if (token.type === 'lparen') {
      this.cursor += 1;
      const node = this.parseOr();
      this.expect('rparen');
      return node;
    }
    if (token.type === 'word') {
      this.cursor += 1;
      const upper = token.value.toUpperCase();
      if (upper === 'TRUE') {
        return { type: 'literal', value: true };
      }
      if (upper === 'FALSE') {
        return { type: 'literal', value: false };
      }
      if (upper === 'NULL') {
        return { type: 'literal', value: null };
      }
      if (this.peek().type === 'lparen') {
        return { type: 'call', name: upper, args: this.parseList() };
      }
      if (KEYWORDS.has(upper)) {
        throw new RuleSyntaxError(`关键字 ${upper} 的位置不正确`, token.position);
      }
      return { type: 'path', path: token.value };
    }

    throw new RuleSyntaxError(
      token.type === 'eof' ? '表达式不完整' : `无法解析的内容：${token.value}`,
      token.position,
    );
  }

  private peek(): Token {
    return this.tokens[this.cursor]!;
  }

  private matchWord(word: string): boolean {
    const token = this.peek();
    if (token.type === 'word' && token.value.toUpperCase() === word) {
      this.cursor += 1;
      return true;
    }
    return false;
  }

  private expect(type: TokenType): void {
    const token = this.peek();
    if (token.type !== type) {
      throw new RuleSyntaxError(`应该是 ${type}，实际是「${token.value}」`, token.position);
    }
    this.cursor += 1;
  }
}

/** 解析规则文本，语法错误会抛 RuleSyntaxError */
export function parseRule(expression: string): RuleNode {
  const trimmed = expression.trim();
  if (!trimmed) {
    throw new RuleSyntaxError('规则表达式为空', 0);
  }
  return new Parser(tokenize(trimmed)).parse();
}

/* ------------------------------ 求值 ------------------------------ */

function isBlank(value: unknown): boolean {
  return value === null || value === undefined || value === '';
}

function truthy(value: unknown): boolean {
  if (Array.isArray(value)) {
    return value.length > 0;
  }
  if (isBlank(value)) {
    return false;
  }
  if (typeof value === 'boolean') {
    return value;
  }
  const numeric = toNumber(value);
  if (Number.isFinite(numeric)) {
    return numeric !== 0;
  }
  return true;
}

/** 两侧都能当数字时按数值比较，否则按字符串比较 */
function compare(left: unknown, right: unknown): number {
  const leftNumber = toNumber(left);
  const rightNumber = toNumber(right);
  if (Number.isFinite(leftNumber) && Number.isFinite(rightNumber)) {
    return leftNumber === rightNumber ? 0 : leftNumber > rightNumber ? 1 : -1;
  }
  const leftText = left === null || left === undefined ? '' : String(left);
  const rightText = right === null || right === undefined ? '' : String(right);
  return leftText === rightText ? 0 : leftText > rightText ? 1 : -1;
}

/** 判定结果类取值统一成 PASS / FAIL：兼容中文、布尔与数字 1/0 */
const PASS_TOKENS = new Set(['PASS', 'TRUE', '合格', 'OK', '1', '是']);
const FAIL_TOKENS = new Set(['FAIL', 'FALSE', '不合格', 'NG', '0', '否']);

function normalizeResultToken(value: unknown): string {
  if (typeof value === 'boolean') {
    return value ? 'PASS' : 'FAIL';
  }
  const text = value === null || value === undefined ? '' : String(value).trim().toUpperCase();
  if (PASS_TOKENS.has(text)) {
    return 'PASS';
  }
  if (FAIL_TOKENS.has(text)) {
    return 'FAIL';
  }
  return '';
}

function mean(values: number[]): number {
  return values.reduce((sum, item) => sum + item, 0) / values.length;
}

/** 样本标准差（n-1），SPC 计算 CP/CPK 用这个口径 */
function standardDeviation(values: number[]): number {
  if (values.length < 2) {
    return Number.NaN;
  }
  const average = mean(values);
  const variance =
    values.reduce((sum, item) => sum + (item - average) ** 2, 0) / (values.length - 1);
  return Math.sqrt(variance);
}

function requireNumbers(name: string, value: unknown): number[] {
  const values = toNumberArray(value);
  if (values.length === 0) {
    throw new RuleRuntimeError(`函数 ${name} 需要数值型参数，实际取到「${String(value)}」`);
  }
  return values;
}

function requireNumber(name: string, value: unknown): number {
  const numeric = toNumber(value);
  if (!Number.isFinite(numeric)) {
    throw new RuleRuntimeError(`函数 ${name} 需要数字参数，实际取到「${String(value)}」`);
  }
  return numeric;
}

/** 白名单函数表，规则文本只能调用这里面的函数 */
const FUNCTIONS: Record<string, (args: unknown[]) => unknown> = {
  ABS: (args) => Math.abs(requireNumber('ABS', args[0])),
  AVG: (args) => mean(requireNumbers('AVG', args[0])),
  COUNT: (args) => (Array.isArray(args[0]) ? args[0].length : toNumberArray(args[0]).length),
  /** 过程能力指数 CP=(USL-LSL)/(6σ) */
  CP: (args) => {
    const values = requireNumbers('CP', args[0]);
    const deviation = standardDeviation(values);
    if (!Number.isFinite(deviation) || deviation === 0) {
      return Number.NaN;
    }
    return (requireNumber('CP', args[1]) - requireNumber('CP', args[2])) / (6 * deviation);
  },
  /** 过程能力指数 CPK=min(USL-μ, μ-LSL)/(3σ) */
  CPK: (args) => {
    const values = requireNumbers('CPK', args[0]);
    const deviation = standardDeviation(values);
    if (!Number.isFinite(deviation) || deviation === 0) {
      return Number.NaN;
    }
    const upper = requireNumber('CPK', args[1]);
    const lower = requireNumber('CPK', args[2]);
    const average = mean(values);
    return Math.min(upper - average, average - lower) / (3 * deviation);
  },
  /** 不合格率（百分比），口径与 PASS_RATE 一致 */
  FAIL_RATE: (args) => {
    const values = Array.isArray(args[0]) ? args[0].flat(Number.POSITIVE_INFINITY) : [args[0]];
    if (values.length === 0) {
      return Number.NaN;
    }
    const failCount = values.filter((item) => normalizeResultToken(item) === 'FAIL').length;
    return Number(((failCount / values.length) * 100).toFixed(2));
  },
  MAX: (args) => Math.max(...requireNumbers('MAX', args[0])),
  MIN: (args) => Math.min(...requireNumbers('MIN', args[0])),
  /** 合格率（百分比，保留两位） */
  PASS_RATE: (args) => {
    const values = Array.isArray(args[0]) ? args[0].flat(Number.POSITIVE_INFINITY) : [args[0]];
    if (values.length === 0) {
      return Number.NaN;
    }
    const passCount = values.filter((item) => normalizeResultToken(item) === 'PASS').length;
    return Number(((passCount / values.length) * 100).toFixed(2));
  },
  ROUND: (args) => {
    const digits = args.length > 1 ? requireNumber('ROUND', args[1]) : 0;
    const factor = 10 ** digits;
    return Math.round(requireNumber('ROUND', args[0]) * factor) / factor;
  },
  STDDEV: (args) => standardDeviation(requireNumbers('STDDEV', args[0])),
  SUM: (args) => requireNumbers('SUM', args[0]).reduce((sum, item) => sum + item, 0),
};

/**
 * 白名单里的全部函数名，供保存前校验与界面提示使用。
 * <p>
 * 不要另抄一份到 UI 上：白名单是引擎的一部分，抄一份就会在加函数时漏改，
 * 用户看到的可用函数列表与引擎实际支持的悄悄对不上。
 */
export function listAllFunctionNames(): string[] {
  return Object.keys(FUNCTIONS);
}

/** 静态检查表达式里调用的函数是否在白名单内，用于保存前校验 */
export function listFunctionNames(node: RuleNode): string[] {
  if (node.type === 'call') {
    return [node.name, ...node.args.flatMap(listFunctionNames)];
  }
  if (node.type === 'unary') {
    return listFunctionNames(node.operand);
  }
  if (node.type === 'binary') {
    return [...listFunctionNames(node.left), ...listFunctionNames(node.right)];
  }
  if (node.type === 'between') {
    return [
      ...listFunctionNames(node.value),
      ...listFunctionNames(node.lower),
      ...listFunctionNames(node.upper),
    ];
  }
  if (node.type === 'in') {
    return [...listFunctionNames(node.value), ...node.items.flatMap(listFunctionNames)];
  }
  return [];
}

/** 未知函数名返回 undefined，供调用方给出可读报错 */
export function findUnknownFunction(node: RuleNode): string | undefined {
  return listFunctionNames(node).find((name) => !FUNCTIONS[name]);
}

function evaluateNode(node: RuleNode, context: unknown): unknown {
  switch (node.type) {
    case 'literal': {
      return node.value;
    }
    case 'path': {
      return readPath(context, node.path);
    }
    case 'call': {
      const handler = FUNCTIONS[node.name];
      if (!handler) {
        throw new RuleRuntimeError(
          `不支持函数 ${node.name}，可用函数：${Object.keys(FUNCTIONS).join('、')}`,
        );
      }
      return handler(node.args.map((arg) => evaluateNode(arg, context)));
    }
    case 'unary': {
      const value = evaluateNode(node.operand, context);
      return node.operator === '!' ? !truthy(value) : -toNumber(value);
    }
    case 'between': {
      const value = evaluateNode(node.value, context);
      const hit =
        compare(value, evaluateNode(node.lower, context)) >= 0 &&
        compare(value, evaluateNode(node.upper, context)) <= 0;
      return node.negated ? !hit : hit;
    }
    case 'in': {
      const value = evaluateNode(node.value, context);
      const hit = node.items.some(
        (item) => compare(value, evaluateNode(item, context)) === 0,
      );
      return node.negated ? !hit : hit;
    }
    case 'binary': {
      return evaluateBinary(node, context);
    }
    default: {
      throw new RuleRuntimeError('规则节点类型不支持');
    }
  }
}

function evaluateBinary(
  node: Extract<RuleNode, { type: 'binary' }>,
  context: unknown,
): unknown {
  if (node.operator === 'AND') {
    return truthy(evaluateNode(node.left, context)) && truthy(evaluateNode(node.right, context));
  }
  if (node.operator === 'OR') {
    return truthy(evaluateNode(node.left, context)) || truthy(evaluateNode(node.right, context));
  }

  const left = evaluateNode(node.left, context);
  const right = evaluateNode(node.right, context);

  switch (node.operator) {
    case '=': {
      return compare(left, right) === 0;
    }
    case '!=': {
      return compare(left, right) !== 0;
    }
    case '>': {
      return compare(left, right) > 0;
    }
    case '>=': {
      return compare(left, right) >= 0;
    }
    case '<': {
      return compare(left, right) < 0;
    }
    case '<=': {
      return compare(left, right) <= 0;
    }
    case '+': {
      const leftNumber = toNumber(left);
      const rightNumber = toNumber(right);
      // 两侧都是数值才做加法，否则按文本拼接（编号类字段常用）
      if (Number.isFinite(leftNumber) && Number.isFinite(rightNumber)) {
        return leftNumber + rightNumber;
      }
      return `${left ?? ''}${right ?? ''}`;
    }
    case '-': {
      return toNumber(left) - toNumber(right);
    }
    case '*': {
      return toNumber(left) * toNumber(right);
    }
    case '/': {
      const divisor = toNumber(right);
      if (divisor === 0) {
        throw new RuleRuntimeError('规则里出现了除以 0');
      }
      return toNumber(left) / divisor;
    }
    default: {
      throw new RuleRuntimeError(`不支持的运算符 ${node.operator}`);
    }
  }
}

/** 求值，返回原始结果（可能是数字、字符串、布尔） */
export function evaluateRule(expression: string, context: unknown): unknown {
  return evaluateNode(parseRule(expression), context);
}

/** 求值并转成布尔判定，用于「是否合格」这类条件 */
export function evaluateCondition(expression: string, context: unknown): boolean {
  return truthy(evaluateRule(expression, context));
}

/** 对已解析的语法树求值，避免同一条规则在多行上重复解析 */
export function evaluateRuleNode(node: RuleNode, context: unknown): unknown {
  return evaluateNode(node, context);
}

/** 对已解析的语法树做布尔判定 */
export function evaluateConditionNode(node: RuleNode, context: unknown): boolean {
  return truthy(evaluateRuleNode(node, context));
}

/** 解析后的路径依赖，渲染前可据此检查数据是否齐备 */
export function listRulePaths(node: RuleNode): string[] {
  const paths: string[] = [];
  const visit = (current: RuleNode): void => {
    switch (current.type) {
      case 'path': {
        paths.push(current.path);
        return;
      }
      case 'call': {
        current.args.forEach(visit);
        return;
      }
      case 'unary': {
        visit(current.operand);
        return;
      }
      case 'binary': {
        visit(current.left);
        visit(current.right);
        return;
      }
      case 'between': {
        visit(current.value);
        visit(current.lower);
        visit(current.upper);
        return;
      }
      case 'in': {
        visit(current.value);
        current.items.forEach(visit);
        return;
      }
      default: {
        return;
      }
    }
  };
  visit(node);
  // 统计函数作用在数组路径上，这里去掉末端字段只保留数组本身
  return [...new Set(paths.map((path) => parsePath(path).join('.')))];
}