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

import type { MeasureWidthImpl } from '../types';
import { MissingFontSpecError } from '../errors';
import { buildFontSpecKey } from '../fontSpecKey';
import { measureSliceWidthCached } from '../measureSliceWidthCached';
import { measureWidthCached } from '../measureWidthCached';

describe('textWrap width-measurement', () => {
  it('buildFontSpecKey: 缺字段必须报错（简体中文）', () => {
    expect(() => buildFontSpecKey({ fontFamily: 'PingFangSC-Regular', fontWeight: '400' } as any)).toThrow(
      MissingFontSpecError
    );
  });

  it('measureWidthCached: 未提供测量能力 -> approx + WIDTH_UNKNOWN', async () => {
    const res = await measureWidthCached({
      text: 'hello',
      context: 'WIDGET',
      contextProfile: 'WIDGET',
      fontSpec: null,
      measureWidthImpl: undefined,
    });
    expect(res.width).toBeNull();
    expect(res.meta).toEqual({ isApprox: true, reason: 'WIDTH_UNKNOWN' });
  });

  it('measureWidthCached: 同 key 命中缓存（不会重复调用底层测量）', async () => {
    const impl: MeasureWidthImpl = vi.fn(async ({ text }) => text.length);

    const a = await measureWidthCached({
      text: 'abc',
      context: 'APP',
      contextProfile: 'APP|ios',
      fontSpec: { fontFamily: 'PingFangSC-Regular', fontWeight: '400', fontSize: 16 },
      measureWidthImpl: impl,
    });
    const b = await measureWidthCached({
      text: 'abc',
      context: 'APP',
      contextProfile: 'APP|ios',
      fontSpec: { fontFamily: 'PingFangSC-Regular', fontWeight: '400', fontSize: 16 },
      measureWidthImpl: impl,
    });

    expect(a.width).toBe(3);
    expect(b.width).toBe(3);
    expect(impl).toHaveBeenCalledTimes(1);
  });

  it('measureWidthCached: 测量抛错 -> approx + MEASURE_FAILED', async () => {
    const impl: MeasureWidthImpl = vi.fn(async () => {
      throw new Error('boom');
    });

    const res = await measureWidthCached({
      text: 'abc',
      context: 'APP',
      contextProfile: 'APP|ios|throw',
      fontSpec: { fontFamily: 'PingFangSC-Regular', fontWeight: '400', fontSize: 16 },
      measureWidthImpl: impl,
    });

    expect(res.width).toBeNull();
    expect(res.meta).toEqual({ isApprox: true, reason: 'MEASURE_FAILED' });
  });

  it('measureSliceWidthCached: 同 sliceKey 命中缓存', async () => {
    const impl: MeasureWidthImpl = vi.fn(async ({ text }) => text.length);

    const tokens = [
      { text: 'I', start: 0, end: 1 },
      { text: 'am', start: 2, end: 4 },
      { text: 'tired', start: 5, end: 10 },
    ];

    const a = await measureSliceWidthCached({
      tokens: tokens as any,
      start: 0,
      end: 2,
      context: 'APP',
      contextProfile: 'APP|ios',
      fontSpec: { fontFamily: 'PingFangSC-Regular', fontWeight: '400', fontSize: 16 },
      measureWidthImpl: impl,
    });
    const b = await measureSliceWidthCached({
      tokens: tokens as any,
      start: 0,
      end: 2,
      context: 'APP',
      contextProfile: 'APP|ios',
      fontSpec: { fontFamily: 'PingFangSC-Regular', fontWeight: '400', fontSize: 16 },
      measureWidthImpl: impl,
    });

    expect(a.width).toBe('I am'.length);
    expect(b.width).toBe('I am'.length);
    expect(impl).toHaveBeenCalledTimes(1);
  });
});

