import type { ContextProfile, FontSpec, MeasureResult, MeasureWidthImpl, TextWrapContext } from './types';
import type { Token } from '../core/types';
import { LruCache } from './cache';
import { assertFontSpecComplete, buildFontSpecKey } from './fontSpecKey';
import { joinTokens } from '../core/joinTokens';
import { measureWidthCached } from './measureWidthCached';

// 切片缓存：同一 (start,end,fontSpecKey,contextProfile) 的宽度只测一次
const sliceWidthCache = new LruCache<Promise<number>>(4000);

function fnv1a32(input: string): string {
  // 32-bit FNV-1a（确定性、实现简单；用于缓存 key 避免过长）
  let hash = 0x811c9dc5;
  for (let i = 0; i < input.length; i++) {
    hash ^= input.charCodeAt(i);
    // hash *= 16777619（用位运算模拟 32-bit 溢出）
    hash = (hash + (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24)) >>> 0;
  }
  return hash.toString(16).padStart(8, '0');
}

function buildSliceKey(args: {
  contextProfile: ContextProfile;
  fontSpecKey: string;
  start: number;
  end: number;
  sliceTextHash: string;
}): string {
  // 注意：必须包含 sliceText 的信息，否则不同文本的相同 (start,end) 会发生跨文本串缓存
  return `${args.contextProfile}|${args.fontSpecKey}|${args.start}|${args.end}|${args.sliceTextHash}`;
}

/**
 * 测量 token 切片 `[start..end)` 的宽度（带缓存）。
 *
 * - 切片文本通过 `joinTokens()` 生成（保证与 core-contract 的重组口径一致）
 * - 内部复用 `measureWidthCached` 的降级语义
 */
export async function measureSliceWidthCached(args: {
  tokens: Token[];
  start: number;
  end: number;
  context: TextWrapContext;
  contextProfile: ContextProfile;
  fontSpec?: Partial<FontSpec> | null;
  measureWidthImpl?: MeasureWidthImpl;
  widgetEnableMeasure?: boolean;
  rawSeparators?: string[];
}): Promise<MeasureResult> {
  const enabled =
    typeof args.measureWidthImpl === 'function' && (args.context === 'APP' || args.widgetEnableMeasure === true);

  if (!enabled) {
    // 不启用测量时直接走 width=null（保持与 measureWidthCached 一致）
    return { width: null, meta: { isApprox: true, reason: 'WIDTH_UNKNOWN' } };
  }

  assertFontSpecComplete(args.fontSpec);
  const fontSpecKey = buildFontSpecKey(args.fontSpec);
  const sliceText = joinTokens(args.tokens, args.start, args.end, args.rawSeparators);
  const key = buildSliceKey({
    contextProfile: args.contextProfile,
    fontSpecKey,
    start: args.start,
    end: args.end,
    sliceTextHash: fnv1a32(sliceText),
  });

  const cached = sliceWidthCache.get(key);
  if (cached) {
    try {
      const w = await cached;
      return { width: w, meta: { isApprox: false } };
    } catch {
      return { width: null, meta: { isApprox: true, reason: 'MEASURE_FAILED' } };
    }
  }

  const promise = (async () => {
    const res = await measureWidthCached({
      text: sliceText,
      context: args.context,
      contextProfile: args.contextProfile,
      fontSpec: args.fontSpec,
      measureWidthImpl: args.measureWidthImpl,
      widgetEnableMeasure: args.widgetEnableMeasure,
    });

    if (res.width === null) {
      throw new Error('切片测量失败或不可用。');
    }
    return res.width;
  })();

  sliceWidthCache.set(key, promise);

  try {
    const w = await promise;
    return { width: w, meta: { isApprox: false } };
  } catch {
    sliceWidthCache.delete(key);
    return { width: null, meta: { isApprox: true, reason: 'MEASURE_FAILED' } };
  }
}

