/**
 * 组件列定义 / 表头跨列的字符串解析。
 * <p>
 * 「列」这件事必须能被编码成**一个标量字符串**：组件业务属性的类型只有
 * boolean / enum / number / string / text（见 {@link QualityFieldSchema}），
 * 最终还会落到画布节点的 attributes 上，而那里也是字符串映射。
 * 用 JSON 编码列数组会要求模型输出转义后的嵌套 JSON，是它出错的重灾区；
 * 一行 `标题=绑定|标题=绑定` 信息量相同，模型、人都好写。
 * <p>
 * 必须单行：这串东西最终是 HTML 属性值，换行在属性里合法但容易被
 * 编辑器/序列化环节改写，统一按单行处理。
 * <p>
 * 解析放在这里而不是各组件内部，是因为校验列与表头跨列都依赖同一套语法，
 * 两份实现迟早漂移，而漂移的表现是「列数对不上」这种很难查的渲染事故。
 */

/** 一列：表头文字 + 行内取值表达式 */
export interface QualityColumnSpec {
  /** 表头文字，已剥掉合并标记前缀 */
  label: string;
  /** 行内取值表达式，如 {{item.itemName}} */
  expr: string;
  /** 是否纵向合并跨住整组（标题前写了 #） */
  merge: boolean;
}

/** 上层表头的一个单元格 */
export interface QualityHeaderSpan {
  label: string;
  span: number;
}

/** 解析结果：能解析出多少算多少，错误逐条回报，由调用方决定是否拦下保存 */
export interface QualitySpecParseResult<T> {
  items: T[];
  errors: string[];
}

/** 列与列之间的分隔符 */
const SEGMENT_SEPARATOR = '|';

/** 合并列的标记前缀 */
const MERGE_MARK = '#';

/** 表头跨列的计数标记 */
const SPAN_MARK = '^';

/**
 * 解析列定义串。
 *
 * @param spec 形如 `序号={{index}}|#检验项目={{item.itemName}}|子项={{item.group.childName}}`
 * @returns 列定义与错误清单；空串返回空数组，由调用方回落到组件默认列
 */
export function parseColumnSpec(spec: string | undefined): QualitySpecParseResult<QualityColumnSpec> {
  const errors: string[] = [];
  const items: QualityColumnSpec[] = [];
  const segments = splitSegments(spec);
  segments.forEach((segment, index) => {
    const position = `第 ${index + 1} 段「${segment}」`;
    const separatorAt = segment.indexOf('=');
    if (separatorAt < 0) {
      errors.push(`${position}缺少「=」，每一列都要写成「列标题=绑定表达式」`);
      return;
    }
    const rawLabel = segment.slice(0, separatorAt).trim();
    const expr = segment.slice(separatorAt + 1).trim();
    const merge = rawLabel.startsWith(MERGE_MARK);
    const label = merge ? rawLabel.slice(MERGE_MARK.length).trim() : rawLabel;
    if (!label) {
      errors.push(`${position}的列标题是空的，请补上打印在表头上的文字`);
      return;
    }
    if (!expr) {
      errors.push(`${position}的绑定表达式是空的，请补上「=」后面的内容，如 {{item.itemName}}`);
      return;
    }
    items.push({ label, expr, merge });
  });
  return { items, errors };
}

/**
 * 解析上层表头串。
 *
 * @param spec        形如 `检验结果^5|结论^1`；写不出 `^N` 的按 1 列算，靠总数校验兜底
 * @param columnCount 下层实际列数，用来校验各段跨列数之和
 */
export function parseHeaderSpans(
  spec: string | undefined,
  columnCount: number,
): QualitySpecParseResult<QualityHeaderSpan> {
  const errors: string[] = [];
  const items: QualityHeaderSpan[] = [];
  const segments = splitSegments(spec);
  // 没填就是「不要上层表头」，不是「跨列数合计为 0」——不能拿它去撞总数校验
  if (segments.length === 0) {
    return { items, errors };
  }
  segments.forEach((segment, index) => {
    const position = `第 ${index + 1} 段「${segment}」`;
    const markAt = segment.lastIndexOf(SPAN_MARK);
    const rawLabel = (markAt < 0 ? segment : segment.slice(0, markAt)).trim();
    const rawSpan = markAt < 0 ? '' : segment.slice(markAt + 1).trim();
    if (!rawLabel) {
      errors.push(`${position}的表头文字是空的，请补上跨越这几列的分组标题`);
      return;
    }
    const span = rawSpan ? Number(rawSpan) : 1;
    if (!Number.isInteger(span) || span < 1) {
      errors.push(`${position}的跨列数「${rawSpan}」不是正整数，请写成如 检验结果^5 的形式`);
      return;
    }
    items.push({ label: rawLabel, span });
  });
  if (errors.length > 0) {
    return { items, errors };
  }
  const total = items.reduce((sum, item) => sum + item.span, 0);
  if (total !== columnCount) {
    errors.push(
      `表头跨列数合计 ${total}，检验表的实际列数为 ${columnCount}，两者必须相等。` +
        `请检查「表头跨列」里各段末尾 ^ 后面的数字`,
    );
  }
  return { items, errors };
}

/** 按 `|` 切段：空段（首尾多写了分隔符）直接跳过，不当作错误，避免手滑就报错 */
function splitSegments(spec: string | undefined): string[] {
  if (!spec) {
    return [];
  }
  return spec
    .split(SEGMENT_SEPARATOR)
    .map((segment) => segment.trim())
    .filter((segment) => segment.length > 0);
}
