/**
 * 报告判定
 * <p>
 * 在渲染前把检验项算成 PASS/FAIL，并汇总出报告结论与合格率。
 * 判定只发生在这里，组件与模板都不做计算，保证同一份数据在任何模板下判定一致。
 */
import type { QualityResult, ReportContext, ReportContextItem } from './context';
import type { RuleNode } from './rule-engine';

import { QUALITY_RESULT, QUALITY_RESULT_TEXT } from './context';
import { toNumber } from './path';
import {
  evaluateConditionNode,
  findUnknownFunction,
  listAllFunctionNames,
  parseRule,
  RuleSyntaxError,
} from './rule-engine';

/** 规则作用域：item 逐项判定，report 报告级汇总 */
export type QualityRuleScope = 'item' | 'report';

export interface QualityRuleDefinition {
  /** 规则标识，用于出错时定位 */
  id?: string;
  /** 规则名称 */
  name?: string;
  scope: QualityRuleScope;
  /** 判定表达式，求值为真视为合格 */
  expression: string;
  /** 该规则是否参与判定，默认 true */
  enabled?: boolean;
}

/** 待判定标记：规则本身出错时用，避免把不确定当成合格 */
const PENDING_TEXT = '待判定';

/**
 * 无判定规则标记：既没有规则、也没有规格上下限。
 * <p>
 * 和「待判定」刻意分开：待判定是「本该判、但规则跑挂了」，仍留在合格率分母里；
 * 无判定规则是「这项根本没有判定依据」，不计入合格率分母、也不参与报告结论。
 */
const UNDECIDABLE_TEXT = '无判定规则';

/**
 * 校验入参：只用到这三个字段，且都允许缺省。
 * <p>
 * 不复用 {@link QualityRuleDefinition}——那里的 expression 是必填的，
 * 而设计器里正在编辑的规则、以及历史数据里的规则项都可能是空的，
 * 校验函数恰恰要能接住这种「还没写完」的入参。
 */
export interface RuleValidationInput {
  id?: string;
  name?: string;
  expression?: string;
}

/**
 * 保存前校验规则文本：语法与函数白名单。
 * <p>
 * 两块都要拦。只拦语法的话，{@code FOO(1)} 这种调用会一路存进库里，
 * 之后每张报告都带一条「无法执行」，而用户在设计器里看不出哪里不对。
 * 返回的问题清单与后端 {@code QualityReportEngine.validateRules} 同口径。
 */
export function validateRule(rule: RuleValidationInput): string[] {
  const errors: string[] = [];
  const label = rule.name?.trim() || rule.id || '未命名规则';
  if (!rule.expression?.trim()) {
    errors.push(`规则「${label}」的表达式为空`);
    return errors;
  }

  let node: RuleNode;
  try {
    node = parseRule(rule.expression);
  } catch (error) {
    if (error instanceof RuleSyntaxError) {
      errors.push(`规则「${label}」语法错误：${error.message}（第 ${error.position + 1} 个字符）`);
    } else {
      errors.push(`规则「${label}」无法解析：${(error as Error).message}`);
    }
    return errors;
  }

  const unknown = findUnknownFunction(node);
  if (unknown) {
    errors.push(
      `规则「${label}」调用了不支持的函数 ${unknown}，可用函数：${listAllFunctionNames().join('、')}`,
    );
  }
  return errors;
}

/** 用规格上下限判定单项：实测值不在区间内即不合格 */
function evaluateByLimit(item: ReportContextItem): boolean | undefined {
  const actual = toNumber(item.actualValue);
  if (!Number.isFinite(actual)) {
    return undefined;
  }
  const hasUpper = typeof item.upperLimit === 'number' && Number.isFinite(item.upperLimit);
  const hasLower = typeof item.lowerLimit === 'number' && Number.isFinite(item.lowerLimit);
  if (!hasUpper && !hasLower) {
    return undefined;
  }
  if (hasUpper && actual > item.upperLimit!) {
    return false;
  }
  if (hasLower && actual < item.lowerLimit!) {
    return false;
  }
  return true;
}

function normalizeExisting(result: string): QualityResult | undefined {
  const text = result?.trim().toUpperCase();
  if (text === QUALITY_RESULT.PASS) {
    return QUALITY_RESULT.PASS;
  }
  if (text === QUALITY_RESULT.FAIL) {
    return QUALITY_RESULT.FAIL;
  }
  return undefined;
}

function applyResult(item: ReportContextItem, result: QualityResult | undefined): void {
  if (!result) {
    item.result = '';
    item.resultText = PENDING_TEXT;
    return;
  }
  item.result = result;
  item.resultText = QUALITY_RESULT_TEXT[result];
}

/**
 * 没有判定依据：置为「无判定规则」。
 * <p>
 * 报告里常有「试样质量 m」「称量瓶 m0」这类只供公式取数的过程参数，它们没有规格也就无从判定；
 * 若照常算进分母，一份本来全合格的报告会因为几个过程参数被压成「待判定」。
 */
function applyUndecidable(item: ReportContextItem): void {
  item.result = '';
  item.resultText = UNDECIDABLE_TEXT;
}

function formatPassRate(passCount: number, total: number): string {
  if (total === 0) {
    return '';
  }
  return `${((passCount / total) * 100).toFixed(2)}%`;
}

export interface EvaluationOutcome {
  context: ReportContext;
  /** 规则的问题清单，渲染时应在报告上显式暴露，不能悄悄吞掉 */
  errors: string[];
}

/**
 * 计算检验项判定与报告汇总。
 * <p>
 * 单个检验项的判定优先级：item 作用域规则 → 规格上下限 → 数据自带结果。
 * 报告级判定默认取「全部检验项合格」，有 report 作用域规则时以规则为准。
 */
export function evaluateReport(
  context: ReportContext,
  rules: QualityRuleDefinition[] = [],
): EvaluationOutcome {
  const errors: string[] = [];
  const itemRules = rules.filter((rule) => rule.scope === 'item' && rule.enabled !== false);
  const reportRules = rules.filter((rule) => rule.scope === 'report' && rule.enabled !== false);

  const items = context.inspectionItems.map((item) => ({ ...item }));
  /** 无判定依据的项数，只用来把这类项从合格率分母与报告结论里摘出去 */
  let undecidableCount = 0;

  items.forEach((item, index) => {
    if (itemRules.length > 0) {
      let passed = true;
      let failed = false;
      itemRules.forEach((rule) => {
        // 逐项规则在「单项上下文」里求值，item 指向当前行
        const scoped = { ...context, report: context.report, item, index: index + 1 };
        try {
          if (!evaluateConditionNode(compiledRule(rule.expression), scoped)) {
            passed = false;
          }
        } catch (error) {
          failed = true;
          errors.push(
            `第 ${index + 1} 项「${item.itemName}」的规则「${rule.name ?? rule.id ?? ''}」无法执行：${(error as Error).message}`,
          );
        }
      });
      applyResult(item, failed ? undefined : passed ? QUALITY_RESULT.PASS : QUALITY_RESULT.FAIL);
      return;
    }

    const byLimit = evaluateByLimit(item);
    if (byLimit !== undefined) {
      applyResult(item, byLimit ? QUALITY_RESULT.PASS : QUALITY_RESULT.FAIL);
      return;
    }

    const existing = normalizeExisting(item.result);
    if (!existing) {
      // 既没有规则、也没有判定依据：如实标出来，不要默认合格
      applyUndecidable(item);
      undecidableCount++;
      errors.push(
        `第 ${index + 1} 项「${item.itemName || '未命名'}」没有规格上下限也没有判定规则，无法判定`,
      );
      return;
    }
    applyResult(item, existing);
  });

  const passCount = items.filter((item) => item.result === QUALITY_RESULT.PASS).length;
  const failCount = items.filter((item) => item.result === QUALITY_RESULT.FAIL).length;
  const total = items.length;
  /** 合格率的分母只算「能判定」的项，无判定规则的项既不加分也不减分 */
  const decidableCount = total - undecidableCount;

  let reportResult: QualityResult | undefined;
  if (reportRules.length > 0) {
    let passed = true;
    let failed = false;
    reportRules.forEach((rule) => {
      const scoped = { ...context, inspectionItems: items, index: 0 };
      try {
        if (!evaluateConditionNode(compiledRule(rule.expression), scoped)) {
          passed = false;
        }
      } catch (error) {
        failed = true;
        errors.push(`报告级规则「${rule.name ?? rule.id ?? ''}」无法执行：${(error as Error).message}`);
      }
    });
    reportResult = failed ? undefined : passed ? QUALITY_RESULT.PASS : QUALITY_RESULT.FAIL;
  } else if (decidableCount > 0) {
    // 只有「全部可判定项都合格」才给合格结论。无判定规则的项不参与，它们没有对错可言。
    // 存在待判定项（规则跑挂）时不给结论：没验过不能说合格，没证据也不能说不合格，
    // 否则会出现「结论合格、合格率 0%」这种自相矛盾的报告。
    if (failCount > 0) {
      reportResult = QUALITY_RESULT.FAIL;
    } else if (passCount === decidableCount) {
      reportResult = QUALITY_RESULT.PASS;
    }
  }

  const report = {
    ...context.report,
    total,
    passCount,
    failCount,
    passRate: formatPassRate(passCount, decidableCount),
    result: reportResult ?? '',
    resultText: reportResult ? QUALITY_RESULT_TEXT[reportResult] : PENDING_TEXT,
    conclusion: reportResult ? QUALITY_RESULT_TEXT[reportResult] : PENDING_TEXT,
  };

  return { context: { report, inspectionItems: items }, errors };
}

/** 校验过的规则只解析一次，多行复用时直接对语法树求值 */
const ruleCache = new Map<string, RuleNode>();

function compiledRule(expression: string): RuleNode {
  const cached = ruleCache.get(expression);
  if (cached) {
    return cached;
  }
  const node = parseRule(expression);
  ruleCache.set(expression, node);
  return node;
}