/**
 * Maps JavaScript UTF-16 source positions to canonical UTF-8 byte offsets.
 * Non-ASCII input gets a per-parse table discarded with the compiler tree;
 * ASCII is already an identity mapping and allocates no table.
 */
export class Utf8SourceIndex {
    source;
    byteAtUtf16;
    constructor(source) {
        this.source = source;
        let hasNonAscii = false;
        for (let offset = 0; offset < source.length; offset += 1) {
            if (source.charCodeAt(offset) > 0x7f) {
                hasNonAscii = true;
                break;
            }
        }
        if (!hasNonAscii)
            return;
        this.byteAtUtf16 = new Uint32Array(source.length + 1);
        let bytes = 0;
        let offset = 0;
        while (offset < source.length) {
            this.byteAtUtf16[offset] = bytes;
            const codePoint = source.codePointAt(offset);
            if (codePoint === undefined)
                break;
            const width = codePoint > 0xffff ? 2 : 1;
            const byteWidth = Buffer.byteLength(String.fromCodePoint(codePoint), 'utf8');
            if (width === 2)
                this.byteAtUtf16[offset + 1] = bytes;
            bytes += byteWidth;
            offset += width;
            this.byteAtUtf16[offset] = bytes;
        }
        this.byteAtUtf16[source.length] = bytes;
    }
    byteOffset(utf16Offset) {
        const bounded = Math.max(0, Math.min(this.source.length, utf16Offset));
        if (!this.byteAtUtf16)
            return bounded;
        return this.byteAtUtf16[bounded] ?? 0;
    }
}
export function typeScriptNodeSpan(sourceFile, sourceIndex, node) {
    const startOffset = node.getStart(sourceFile);
    const endOffset = node.getEnd();
    const start = sourceFile.getLineAndCharacterOfPosition(startOffset);
    const end = sourceFile.getLineAndCharacterOfPosition(endOffset);
    return {
        version: 1,
        offsetEncoding: 'utf8-bytes',
        endExclusive: true,
        startLine: start.line + 1,
        startColumn: start.character + 1,
        endLine: end.line + 1,
        endColumn: end.character + 1,
        startByte: sourceIndex.byteOffset(startOffset),
        endByte: sourceIndex.byteOffset(endOffset),
        exact: true,
    };
}
export function treeSitterNodeSpan(node, sourceIndex) {
    // web-tree-sitter parses JavaScript strings through `String#slice`, so its
    // node indices are JavaScript UTF-16 offsets even though the upstream API
    // describes indices generically as byte offsets. Convert them to the public
    // UTF-8 contract just as we do for TypeScript compiler positions.
    return {
        version: 1,
        offsetEncoding: 'utf8-bytes',
        endExclusive: true,
        startLine: node.startPosition.row + 1,
        startColumn: node.startPosition.column + 1,
        endLine: node.endPosition.row + 1,
        endColumn: node.endPosition.column + 1,
        startByte: sourceIndex.byteOffset(node.startIndex),
        endByte: sourceIndex.byteOffset(node.endIndex),
        exact: true,
    };
}
export function inexactLineSpan(startLine, endLine = startLine) {
    return {
        version: 1,
        offsetEncoding: 'utf8-bytes',
        endExclusive: true,
        startLine,
        endLine,
        exact: false,
    };
}
export function sliceUtf8Span(source, span) {
    if (!span.exact || span.startByte == null || span.endByte == null)
        return undefined;
    if (!Number.isSafeInteger(span.startByte) || !Number.isSafeInteger(span.endByte))
        return undefined;
    const bytes = Buffer.from(source, 'utf8');
    if (span.startByte < 0 || span.endByte < span.startByte || span.endByte > bytes.length)
        return undefined;
    const selected = bytes.subarray(span.startByte, span.endByte);
    const decoded = selected.toString('utf8');
    // A coordinate inside a multi-byte code point would decode with U+FFFD and
    // hashing that replacement text would no longer hash the promised raw span.
    // Length alone is not enough: a 4-byte code point truncated after 3 bytes
    // decodes to a single 3-byte U+FFFD, so the round trip must compare bytes.
    if (Buffer.compare(Buffer.from(decoded, 'utf8'), selected) !== 0)
        return undefined;
    return decoded;
}
//# sourceMappingURL=source-span.js.map