/**
 * 数据绑定：模板文本里的 {{path}}
 * <p>
 * 只做取值与替换，不做任何表达式求值；需要计算的场景交给规则引擎。
 * 取不到的路径渲染为空字符串——报告上不该出现用户看不懂的 {{}}。
 */
import { formatValue, readPath } from './path';

/** 绑定表达式：{{ report.reportNo }} */
const BINDING_PATTERN = /\{\{\s*([^{}]+?)\s*\}\}/g;

/** 提取文本里出现的所有绑定路径（去重），用于渲染前的数据依赖检查 */
export function listBindingPaths(template: string): string[] {
  const paths = new Set<string>();
  for (const match of template.matchAll(BINDING_PATTERN)) {
    const path = match[1]?.trim();
    if (path) {
      paths.add(path);
    }
  }
  return [...paths];
}

/** 解析单个绑定路径，取不到返回 undefined */
export function resolveBinding(path: string, context: unknown): unknown {
  return readPath(context, path.trim());
}

/**
 * 替换文本里的全部绑定表达式，并回报取不到值的路径。
 * <p>
 * 渲染时绑定不上通常意味着数据缺口，静默渲染成空会让报告看起来"正常但缺内容"，
 * 所以把缺口交给调用方，由渲染引擎汇总成可见的问题清单。
 */
export function resolveTextWithReport(
  template: string,
  context: unknown,
  onMissing: (path: string) => void,
): string {
  return template.replaceAll(BINDING_PATTERN, (_, rawPath: string) => {
    const path = rawPath.trim();
    const value = readPath(context, path);
    if (value === undefined || value === null) {
      onMissing(path);
    }
    return formatValue(value);
  });
}

/** 替换文本里的全部绑定表达式；未绑定的路径渲染为空 */
export function resolveText(template: string, context: unknown): string {
  return resolveTextWithReport(template, context, () => {});
}

/** 判断一段文本是否是纯绑定表达式（{{path}} 单占一段），是则返回路径 */
export function asSingleBinding(template: string): string | undefined {
  const trimmed = template.trim();
  const match = /^\{\{\s*([^{}]+?)\s*\}\}$/.exec(trimmed);
  return match?.[1]?.trim();
}