import GraphemeSplitter from 'grapheme-splitter';

/**
 * fallback 策略：使用 grapheme-splitter 分割 grapheme clusters。
 *
 * 选择原因：
 * - 避免手写不完整的 Unicode 规则导致“漏拆/误拆”
 * - 作为 Intl.Segmenter 不可用时的稳定兜底
 *
 * 注意：
 * - grapheme-splitter 对少数较新的 emoji ZWJ 序列支持可能不完整（例如 `😮‍💨`）。
 * - 为满足本项目“至少不拆 ZWJ sequence/VS16/肤色修饰符/组合字符”的最低要求，这里做一层确定性的后处理合并。
 */
export function segmentWithFallback(text: string): string[] {
  const splitter = new GraphemeSplitter();
  const raw = splitter.splitGraphemes(text);
  return mergeFallbackClusters(raw);
}

const ZWJ = '\u200D';

function firstCodePoint(s: string): number | null {
  if (!s) return null;
  const cp = s.codePointAt(0);
  return typeof cp === 'number' ? cp : null;
}

function isVariationSelector(cp: number): boolean {
  // VS15/VS16
  return cp === 0xfe0e || cp === 0xfe0f;
}

function isSkinToneModifier(cp: number): boolean {
  return cp >= 0x1f3fb && cp <= 0x1f3ff;
}

function isCombiningMark(cp: number): boolean {
  // 常见 combining marks 范围（覆盖 `e\u0301` 等）
  return (
    (cp >= 0x0300 && cp <= 0x036f) ||
    (cp >= 0x1ab0 && cp <= 0x1aff) ||
    (cp >= 0x1dc0 && cp <= 0x1dff) ||
    (cp >= 0x20d0 && cp <= 0x20ff) ||
    (cp >= 0xfe20 && cp <= 0xfe2f)
  );
}

/**
 * 对 fallback 输出做“最低可用”合并：
 * - 若上一个 cluster 以 ZWJ 结尾，则必须与下一个合并（ZWJ sequence）
 * - 若下一个 cluster 以 VS/肤色修饰符/combining mark 开头，也与上一个合并
 *
 * 目标：满足文档列出的“不拆”组合要求，且行为完全确定性。
 */
function mergeFallbackClusters(raw: string[]): string[] {
  if (!raw.length) return raw;

  const out: string[] = [];
  let buf = raw[0] ?? '';

  for (let i = 1; i < raw.length; i++) {
    const next = raw[i] ?? '';
    const nextCp = firstCodePoint(next);

    const shouldMerge =
      buf.endsWith(ZWJ) ||
      (nextCp !== null && (isVariationSelector(nextCp) || isSkinToneModifier(nextCp) || isCombiningMark(nextCp)));

    if (shouldMerge) {
      buf += next;
      continue;
    }

    out.push(buf);
    buf = next;
  }

  out.push(buf);
  return out;
}

