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

import type { Token } from '../../core/types';
import { buildTieKey, DEFAULT_LEXICONS, DEFAULT_WEIGHTS, scoreLayout } from '../index';

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

function lexicographicLess(a: Array<number | string>, b: Array<number | string>): boolean {
  const n = Math.min(a.length, b.length);
  for (let i = 0; i < n; i++) {
    const av = a[i] as any;
    const bv = b[i] as any;
    if (av < bv) return true;
    if (av > bv) return false;
  }
  return a.length < b.length;
}

describe('textWrap scoring-tiebreak', () => {
  it('EMOTION_SPLIT：拆分情绪短语 -> 强惩罚 + 追加 spanLen（方案 A）', () => {
    const tokens: Token[] = [t('I', 0), t('am', 2), t('so', 5), t('tired', 8)];
    const layoutCandidate = {
      breaks: [3], // I am so | tired
      lines: [
        { start: 0, end: 3, text: 'I am so', width: 10, tokenCount: 3, charCount: 0 },
        { start: 3, end: 4, text: 'tired', width: 10, tokenCount: 1, charCount: 0 },
      ],
    };

    const res = scoreLayout({
      tokens,
      layoutCandidate,
      lang: 'EN',
      context: 'APP',
      availableWidth: 100,
      config: { weights: DEFAULT_WEIGHTS, idealWidthRatio: { APP: 0.9, WIDGET: 0.95 }, ellipsisToken: '…', tcParticleWhitelist: [] },
      lexicons: { ...DEFAULT_LEXICONS, emotionPhrasesEN: ['so tired'] },
      debug: true,
    });

    // 触发强惩罚：-(P_EMOTION_SPLIT + spanLen)
    const first = res.scoreBreakdown?.terms[0];
    expect(first?.key).toBe('EMOTION_SPLIT');
    expect(first?.delta).toBe(-(DEFAULT_WEIGHTS.P_EMOTION_SPLIT + 2));
    expect(res.flags.emotionSplit).toBe(true);
  });

  it('Shift/Accum/Self：EN 词两端带标点也应命中（全词等值匹配）', () => {
    const tokens: Token[] = [t('but,', 0), t('still', 5), t('ok', 11)];
    const layoutCandidate = {
      breaks: [1], // but, | still ok
      lines: [
        // 把 width 设置到 idealWidth 上，避免 length 项干扰 debug Top-3
        { start: 0, end: 1, text: 'but,', width: 90, tokenCount: 1, charCount: 0 },
        { start: 1, end: 3, text: 'still ok', width: 90, tokenCount: 2, charCount: 0 },
      ],
    };

    const res = scoreLayout({
      tokens,
      layoutCandidate,
      lang: 'EN',
      context: 'APP',
      availableWidth: 100, // idealWidth=100*0.9=90
      config: { weights: DEFAULT_WEIGHTS, idealWidthRatio: { APP: 0.9, WIDGET: 0.95 }, ellipsisToken: '…', tcParticleWhitelist: [] },
      lexicons: DEFAULT_LEXICONS,
      debug: true,
    });

    const terms = res.scoreBreakdown?.terms ?? [];
    expect(terms.some((x) => x.key === 'SHIFT_BREAK')).toBe(true);
    expect(terms.some((x) => x.key === 'ACCUM_BREAK')).toBe(true);
  });

  it('10.2G：EmotionWord 与 Accum 同时命中 -> Accum 奖励减半（整数）', () => {
    const tokens: Token[] = [t('already', 0), t('tired', 8)];
    const layoutCandidate = {
      breaks: [1], // already | tired
      lines: [
        { start: 0, end: 1, text: 'already', width: 90, tokenCount: 1, charCount: 0 },
        { start: 1, end: 2, text: 'tired', width: 90, tokenCount: 1, charCount: 0 },
      ],
    };

    const weights = { ...DEFAULT_WEIGHTS, R_EMOTION_TAIL: 10, R_ACCUM_BREAK: 41 }; // 41/2 -> 20（floor）

    const res = scoreLayout({
      tokens,
      layoutCandidate,
      lang: 'EN',
      context: 'APP',
      availableWidth: 100,
      config: { weights, idealWidthRatio: { APP: 0.9, WIDGET: 0.95 }, ellipsisToken: '…', tcParticleWhitelist: [] },
      lexicons: DEFAULT_LEXICONS,
      debug: true,
    });

    const terms = res.scoreBreakdown?.terms ?? [];
    // debug 可能截断为 Top-3，但应包含 EMOTION_TAIL 与 ACCUM_BREAK（顺序确定性）
    const hasEmotion = terms.some((x) => x.key === 'EMOTION_TAIL' && x.delta === 10);
    const hasAccumHalf = terms.some((x) => x.key === 'ACCUM_BREAK' && x.delta === Math.floor(41 / 2));
    expect(hasEmotion).toBe(true);
    expect(hasAccumHalf).toBe(true);
  });

  it('tieKey：lastLineWidth 更大优先（取 -lastLineWidth）', () => {
    const base = {
      breaks: [2],
      lines: [
        { start: 0, end: 2, text: 'a b', width: 50, tokenCount: 2, charCount: 0 },
        { start: 2, end: 3, text: 'c', width: 10, tokenCount: 1, charCount: 0 },
      ],
    };

    const scoredA = { score: 0, flags: {}, tieKey: [] as any };
    const scoredB = { score: 0, flags: {}, tieKey: [] as any };

    const keyShort = buildTieKey({
      scoredLayout: scoredA as any,
      layoutCandidate: base as any,
      lang: 'EN',
      context: 'APP',
      tokenCount: 3,
      availableWidth: 100,
      idealWidthRatio: { APP: 0.9, WIDGET: 0.95 },
    });

    const layoutLongTail = {
      ...base,
      lines: [
        base.lines[0]!,
        { ...base.lines[1]!, width: 30 }, // lastLineWidth 更大
      ],
    };
    const keyLong = buildTieKey({
      scoredLayout: scoredB as any,
      layoutCandidate: layoutLongTail as any,
      lang: 'EN',
      context: 'APP',
      tokenCount: 3,
      availableWidth: 100,
      idealWidthRatio: { APP: 0.9, WIDGET: 0.95 },
    });

    // 更优的 tieKey 应“更小”（因为 lastLineWidth 取负）
    expect(lexicographicLess(keyLong, keyShort)).toBe(true);
  });
});

