/**
 * ANSI-aware text measurement and slicing.
 *
 * Uses native Rust implementation when available, with pure-JS fallbacks
 * for platforms without the native addon.
 */
import { native } from "../native.js";
export { EllipsisKind } from "./types.js";
// ---------------------------------------------------------------------------
// Native availability check
// ---------------------------------------------------------------------------
let _nativeAvailable = null;
function nativeAvailable() {
    if (_nativeAvailable === null) {
        try {
            native.visibleWidth("test");
            _nativeAvailable = true;
        }
        catch {
            _nativeAvailable = false;
        }
    }
    return _nativeAvailable;
}
// ---------------------------------------------------------------------------
// Pure-JS fallbacks
// ---------------------------------------------------------------------------
// ANSI escape sequence regex
const ANSI_RE = /\x1b\[[0-9;]*[a-zA-Z]|\x1b\][^\x07]*\x07|\x1b[()][AB012]|\x1b\[[\?=]?[0-9;]*[a-zA-Z]/g;
function stripAnsi(str) {
    return str.replace(ANSI_RE, "");
}
/**
 * JS fallback: calculate visible width.
 * Handles ANSI stripping, tabs, and basic East Asian wide character detection.
 */
function jsVisibleWidth(text, tabWidth = 3) {
    const clean = stripAnsi(text);
    let width = 0;
    for (const char of clean) {
        if (char === "\t") {
            width += tabWidth;
        }
        else {
            const code = char.codePointAt(0) ?? 0;
            // CJK Unified Ideographs, CJK Compatibility Ideographs, Hangul Syllables,
            // Fullwidth Forms, and other common wide ranges
            if ((code >= 0x1100 && code <= 0x115f) || // Hangul Jamo
                (code >= 0x2e80 && code <= 0x303e) || // CJK Radicals, Kangxi, Ideographic
                (code >= 0x3040 && code <= 0x33bf) || // Hiragana, Katakana, Bopomofo, CJK Compat
                (code >= 0x3400 && code <= 0x4dbf) || // CJK Extension A
                (code >= 0x4e00 && code <= 0xa4cf) || // CJK Unified + Yi
                (code >= 0xa960 && code <= 0xa97c) || // Hangul Jamo Extended-A
                (code >= 0xac00 && code <= 0xd7a3) || // Hangul Syllables
                (code >= 0xf900 && code <= 0xfaff) || // CJK Compatibility Ideographs
                (code >= 0xfe30 && code <= 0xfe6b) || // CJK Compatibility Forms
                (code >= 0xff01 && code <= 0xff60) || // Fullwidth Forms
                (code >= 0xffe0 && code <= 0xffe6) || // Fullwidth Signs
                (code >= 0x1f300 && code <= 0x1f9ff) || // Emoji
                (code >= 0x20000 && code <= 0x2fffd) || // CJK Extension B+
                (code >= 0x30000 && code <= 0x3fffd) // CJK Extension G+
            ) {
                width += 2;
            }
            else if (code < 0x20 || (code >= 0x7f && code < 0xa0)) {
                // Control characters: zero width
                width += 0;
            }
            else {
                width += 1;
            }
        }
    }
    return width;
}
/**
 * JS fallback: truncate text to visible width.
 */
function jsTruncateToWidth(text, maxWidth, ellipsisKind, pad, tabWidth = 3) {
    const ellipsis = ellipsisKind === 0 ? "\u2026" : ellipsisKind === 1 ? "..." : "";
    const ellipsisWidth = ellipsis.length;
    // Fast path: already fits
    if (jsVisibleWidth(text, tabWidth) <= maxWidth) {
        if (pad) {
            const deficit = maxWidth - jsVisibleWidth(text, tabWidth);
            return text + " ".repeat(deficit);
        }
        return text;
    }
    // Need to truncate — rebuild character by character preserving ANSI
    let result = "";
    let width = 0;
    const targetWidth = maxWidth - ellipsisWidth;
    let inAnsi = false;
    let ansiSeq = "";
    for (let i = 0; i < text.length; i++) {
        const ch = text[i];
        // Track ANSI sequences
        if (ch === "\x1b") {
            inAnsi = true;
            ansiSeq = ch;
            continue;
        }
        if (inAnsi) {
            ansiSeq += ch;
            if (/[a-zA-Z]/.test(ch)) {
                inAnsi = false;
                result += ansiSeq;
                ansiSeq = "";
            }
            continue;
        }
        const charWidth = jsVisibleWidth(ch, tabWidth);
        if (width + charWidth > targetWidth) {
            break;
        }
        result += ch;
        width += charWidth;
    }
    result += ellipsis;
    // Reset ANSI at the end
    result += "\x1b[0m";
    if (pad) {
        const currentWidth = width + ellipsisWidth;
        if (currentWidth < maxWidth) {
            result += " ".repeat(maxWidth - currentWidth);
        }
    }
    return result;
}
/**
 * JS fallback: wrap text preserving ANSI.
 */
function jsWrapTextWithAnsi(text, width, _tabWidth = 3) {
    const lines = [];
    for (const rawLine of text.split("\n")) {
        if (jsVisibleWidth(rawLine) <= width) {
            lines.push(rawLine);
            continue;
        }
        // Simple word-break wrapping
        let current = "";
        let currentWidth = 0;
        for (const word of rawLine.split(/(\s+)/)) {
            const wordWidth = jsVisibleWidth(word);
            if (currentWidth + wordWidth > width && currentWidth > 0) {
                lines.push(current);
                current = "";
                currentWidth = 0;
            }
            current += word;
            currentWidth += wordWidth;
        }
        if (current)
            lines.push(current);
    }
    return lines;
}
/**
 * JS fallback: slice by column range.
 */
function jsSliceWithWidth(line, startCol, length, _strict, tabWidth = 3) {
    const clean = stripAnsi(line);
    let col = 0;
    let start = -1;
    let end = clean.length;
    for (let i = 0; i < clean.length; i++) {
        const cw = jsVisibleWidth(clean[i], tabWidth);
        if (start < 0 && col >= startCol)
            start = i;
        if (col >= startCol + length) {
            end = i;
            break;
        }
        col += cw;
    }
    if (start < 0)
        start = clean.length;
    const sliced = clean.slice(start, end);
    return { text: sliced, width: jsVisibleWidth(sliced, tabWidth) };
}
/**
 * JS fallback: extract segments.
 */
function jsExtractSegments(line, beforeEnd, afterStart, afterLen, _strictAfter, tabWidth = 3) {
    const before = jsSliceWithWidth(line, 0, beforeEnd, false, tabWidth);
    const after = jsSliceWithWidth(line, afterStart, afterLen, false, tabWidth);
    return { before: before.text, beforeWidth: before.width, after: after.text, afterWidth: after.width };
}
/**
 * JS fallback: strip ANSI and control chars.
 */
function jsSanitizeText(text) {
    return stripAnsi(text).replace(/[\x00-\x1f\x7f]/g, "").replace(/\r/g, "");
}
// ---------------------------------------------------------------------------
// Exported functions — native with JS fallback
// ---------------------------------------------------------------------------
export function wrapTextWithAnsi(text, width, tabWidth) {
    if (nativeAvailable()) {
        return native.wrapTextWithAnsi(text, width, tabWidth);
    }
    return jsWrapTextWithAnsi(text, width, tabWidth);
}
export function truncateToWidth(text, maxWidth, ellipsisKind, pad, tabWidth) {
    if (nativeAvailable()) {
        return native.truncateToWidth(text, maxWidth, ellipsisKind, pad, tabWidth);
    }
    return jsTruncateToWidth(text, maxWidth, ellipsisKind, pad, tabWidth);
}
export function sliceWithWidth(line, startCol, length, strict, tabWidth) {
    if (nativeAvailable()) {
        return native.sliceWithWidth(line, startCol, length, strict, tabWidth);
    }
    return jsSliceWithWidth(line, startCol, length, strict, tabWidth);
}
export function extractSegments(line, beforeEnd, afterStart, afterLen, strictAfter, tabWidth) {
    if (nativeAvailable()) {
        return native.extractSegments(line, beforeEnd, afterStart, afterLen, strictAfter, tabWidth);
    }
    return jsExtractSegments(line, beforeEnd, afterStart, afterLen, strictAfter, tabWidth);
}
export function sanitizeText(text) {
    if (nativeAvailable()) {
        return native.sanitizeText(text);
    }
    return jsSanitizeText(text);
}
export function visibleWidth(text, tabWidth) {
    if (nativeAvailable()) {
        return native.visibleWidth(text, tabWidth);
    }
    return jsVisibleWidth(text, tabWidth);
}
