export interface CodePointOffsetMap {
  readonly codePointLength: number;
  readonly utf16Length: number;
  readonly codePointToUtf16: readonly number[];
  readonly utf16ToCodePoint: ReadonlyMap<number, number>;
}

export function buildCodePointOffsetMap(value: string): CodePointOffsetMap {
  const codePointToUtf16 = [0];
  const utf16ToCodePoint = new Map<number, number>([[0, 0]]);
  let codePointOffset = 0;
  let utf16Offset = 0;
  for (const character of value) {
    codePointOffset += 1;
    utf16Offset += character.length;
    codePointToUtf16.push(utf16Offset);
    utf16ToCodePoint.set(utf16Offset, codePointOffset);
  }
  return {
    codePointLength: codePointOffset,
    utf16Length: utf16Offset,
    codePointToUtf16,
    utf16ToCodePoint,
  };
}

export function codePointToUtf16(
  map: CodePointOffsetMap,
  offset: number,
): number {
  const normalized = map.codePointToUtf16[offset];
  if (normalized === undefined) {
    throw new RangeError(`Code-point offset ${offset} is outside the input.`);
  }
  return normalized;
}

export function utf16ToCodePoint(
  map: CodePointOffsetMap,
  offset: number,
): number {
  const native = map.utf16ToCodePoint.get(offset);
  if (native === undefined) {
    throw new RangeError(
      `UTF-16 offset ${offset} is outside the input or splits a surrogate pair.`,
    );
  }
  return native;
}
