import { describe, expect, it } from 'vitest';

import { generateBreakpoints } from '../index';

function t(text: string, start: number): { text: string; start: number; end: number } {
  return { text, start, end: start + text.length };
}

describe('textWrap breakpoint-candidates', () => {
  it('EN: tokens=[I,am,so,tired] -> pos=[1,2,3]（升序）', () => {
    const tokens = [t('I', 0), t('am', 2), t('so', 5), t('tired', 8)];
    const { breakpoints, meta } = generateBreakpoints({
      tokens: tokens as any,
      lang: 'EN',
      maxLines: 2,
      config: { tcMaxCandidateBreaks: 80, tcPunctuations: ['，', '。'], balanceRange: 3 },
    });

    expect(breakpoints.map((b) => b.pos)).toEqual([1, 2, 3]);
    expect(meta.originalCount).toBe(3);
    expect(meta.finalCount).toBe(3);
  });

  it('TC: 标点后断点（，）应生成 pos=i+1', () => {
    const tokens = [t('我', 0), t('好', 1), t('累', 2), t('，', 3), t('😮‍💨', 4)];
    const { breakpoints } = generateBreakpoints({
      tokens: tokens as any,
      lang: 'TC',
      maxLines: 2,
      config: { tcMaxCandidateBreaks: 80, tcPunctuations: ['，'], balanceRange: 0 },
    });

    expect(breakpoints.some((b) => b.kind === 'PUNCT' && b.pos === 4)).toBe(true);
  });

  it('TC: forbiddenBreakRanges（闭区间）命中必须剔除（含 start/end）', () => {
    const tokens = [t('我', 0), t('好', 1), t('累', 2), t('，', 3), t('😮‍💨', 4)];
    const { breakpoints } = generateBreakpoints({
      tokens: tokens as any,
      lang: 'TC',
      maxLines: 2,
      constraints: { forbiddenBreakRanges: [{ start: 4, end: 4 }] },
      config: { tcMaxCandidateBreaks: 80, tcPunctuations: ['，'], balanceRange: 0 },
    });

    expect(breakpoints.some((b) => b.pos === 4)).toBe(false);
  });

  it('TC: 去重规则：同 pos 优先保留 priority 更高者；同 priority 按 kind 序', () => {
    // 构造：同一个 pos=2 同时来自 SPACE(priority=20) 和 BALANCE(priority=5)，必须保留 SPACE
    const tokens = [t('我', 0), t(' ', 1), t('好', 2)];
    const { breakpoints } = generateBreakpoints({
      tokens: tokens as any,
      lang: 'TC',
      maxLines: 2,
      config: { tcMaxCandidateBreaks: 80, tcPunctuations: [], balanceRange: 2 },
    });

    const b2 = breakpoints.find((b) => b.pos === 2);
    expect(b2?.kind).toBe('SPACE');
    expect(b2?.priority).toBe(20);
  });

  it('TC: 裁剪上限生效且输出按 pos 升序（确定性）', () => {
    const tokens = Array.from({ length: 60 }).map((_, i) => t('哈', i));
    const res = generateBreakpoints({
      tokens: tokens as any,
      lang: 'TC',
      maxLines: 3,
      config: { tcMaxCandidateBreaks: 8, tcPunctuations: [], balanceRange: 6 },
    });

    expect(res.breakpoints.length).toBe(8);
    // pos 升序
    for (let i = 1; i < res.breakpoints.length; i++) {
      expect(res.breakpoints[i]!.pos).toBeGreaterThanOrEqual(res.breakpoints[i - 1]!.pos);
    }
    // 确定性
    const res2 = generateBreakpoints({
      tokens: tokens as any,
      lang: 'TC',
      maxLines: 3,
      config: { tcMaxCandidateBreaks: 8, tcPunctuations: [], balanceRange: 6 },
    });
    expect(res).toEqual(res2);
  });

  it('边界：N<=1 时不输出任何候选断点', () => {
    const { breakpoints } = generateBreakpoints({
      tokens: [t('我', 0)] as any,
      lang: 'TC',
      maxLines: 2,
      config: { tcMaxCandidateBreaks: 80, tcPunctuations: ['，'], balanceRange: 3 },
    });
    expect(breakpoints).toEqual([]);
  });
});

