/**
 * ============================================================
 *  src/ui/textBuffer.ts —— 输入框文本缓冲（纯逻辑，无 React）
 * ------------------------------------------------------------
 *  把"光标 + 文本"建模成一对值 { text, cursor }：
 *    - text 是 user-perceived 字符串（可含 \n，多行也行）
 *    - cursor 是 **code-point 偏移**（非 UTF-16），保证不会切坏 emoji / 中日韩
 *
 *  所有 op 都是纯函数：返回新的 { text, cursor }，不改入参。
 *  这样 React 端用 useState 包一下就行；测试也直接对纯函数断言、零依赖。
 *
 *  支持的操作：
 *    - 插入：insert / setText / clear
 *    - 删除：deleteBefore（Backspace）/ deleteAt（Delete）
 *           / killToLineStart（Ctrl+U）/ killToLineEnd（Ctrl+K）
 *           / deleteWordBefore（Ctrl+W）
 *    - 移动：moveLeft / moveRight / moveWordLeft / moveWordRight
 *           / moveLineStart / moveLineEnd
 *           / moveBufferStart / moveBufferEnd
 *           / moveUp / moveDown（按逻辑行，跨 \n，保持列位）
 *
 *  词边界定义（够用即可，不学完整 vim/emacs）：
 *    [A-Za-z0-9_] 视为 word char；其他（空格、标点、CJK、emoji）都是 non-word。
 *    word-jump 的语义对齐多数 emacs：先吃当前侧的 non-word，再吃 word。
 * ============================================================
 */
export const EMPTY_BUFFER = { text: '', cursor: 0 };
// ---------- 工具：code-point 视角操作字符串 ----------
/** 把字符串拆成 code-point 数组（Array.from 自动处理 surrogate pair） */
function toCps(s) {
    return Array.from(s);
}
function fromCps(cps) {
    return cps.join('');
}
function isWordChar(c) {
    if (!c)
        return false;
    return /[A-Za-z0-9_]/.test(c);
}
/** 给定 code-point 数组与一个偏移，返回所在逻辑行的起点（指向 \n 之后或 0） */
function findLineStart(cps, idx) {
    let i = idx;
    while (i > 0 && cps[i - 1] !== '\n')
        i--;
    return i;
}
/** 给定 code-point 数组与一个偏移，返回所在逻辑行的终点（指向 \n 或末尾） */
function findLineEnd(cps, idx) {
    let i = idx;
    while (i < cps.length && cps[i] !== '\n')
        i++;
    return i;
}
/** 把 cursor 钳制到合法范围 */
function clamp(cps, idx) {
    return Math.max(0, Math.min(cps.length, idx));
}
// ---------- 插入 ----------
export function bufInsert(s, content) {
    if (content.length === 0)
        return s;
    const cps = toCps(s.text);
    const ins = toCps(content);
    cps.splice(s.cursor, 0, ...ins);
    return { text: fromCps(cps), cursor: s.cursor + ins.length };
}
export function bufSetText(text) {
    return { text, cursor: toCps(text).length };
}
export function bufClear() {
    return EMPTY_BUFFER;
}
// ---------- 删除 ----------
export function bufDeleteBefore(s) {
    if (s.cursor === 0)
        return s;
    const cps = toCps(s.text);
    cps.splice(s.cursor - 1, 1);
    return { text: fromCps(cps), cursor: s.cursor - 1 };
}
export function bufDeleteAt(s) {
    const cps = toCps(s.text);
    if (s.cursor >= cps.length)
        return s;
    cps.splice(s.cursor, 1);
    return { text: fromCps(cps), cursor: s.cursor };
}
/**
 * Ctrl+W / M-DEL：先吃光标左侧的非词字符（空格 / 标点），再吃前一个词。
 * 例：光标贴在 "world" 后 → 删 'world'，留 "hello   "。
 *     光标在 "hello   " 之后 → 先吃 3 空格再吃 "hello"，留空。
 */
export function bufDeleteWordBefore(s) {
    if (s.cursor === 0)
        return s;
    const cps = toCps(s.text);
    let i = s.cursor;
    while (i > 0 && !isWordChar(cps[i - 1]))
        i--;
    while (i > 0 && isWordChar(cps[i - 1]))
        i--;
    cps.splice(i, s.cursor - i);
    return { text: fromCps(cps), cursor: i };
}
/** Ctrl+U：从光标删到当前逻辑行行首 */
export function bufKillToLineStart(s) {
    const cps = toCps(s.text);
    const lineStart = findLineStart(cps, s.cursor);
    if (lineStart === s.cursor)
        return s;
    cps.splice(lineStart, s.cursor - lineStart);
    return { text: fromCps(cps), cursor: lineStart };
}
/** Ctrl+K：从光标删到当前逻辑行行末（不删 \n 自身） */
export function bufKillToLineEnd(s) {
    const cps = toCps(s.text);
    const lineEnd = findLineEnd(cps, s.cursor);
    if (lineEnd === s.cursor)
        return s;
    cps.splice(s.cursor, lineEnd - s.cursor);
    return { text: fromCps(cps), cursor: s.cursor };
}
// ---------- 移动 ----------
export function bufMoveLeft(s) {
    return { text: s.text, cursor: Math.max(0, s.cursor - 1) };
}
export function bufMoveRight(s) {
    const len = toCps(s.text).length;
    return { text: s.text, cursor: Math.min(len, s.cursor + 1) };
}
export function bufMoveWordLeft(s) {
    const cps = toCps(s.text);
    let i = s.cursor;
    while (i > 0 && !isWordChar(cps[i - 1]))
        i--;
    while (i > 0 && isWordChar(cps[i - 1]))
        i--;
    return { text: s.text, cursor: i };
}
export function bufMoveWordRight(s) {
    const cps = toCps(s.text);
    let i = s.cursor;
    while (i < cps.length && !isWordChar(cps[i]))
        i++;
    while (i < cps.length && isWordChar(cps[i]))
        i++;
    return { text: s.text, cursor: i };
}
export function bufMoveLineStart(s) {
    const cps = toCps(s.text);
    return { text: s.text, cursor: findLineStart(cps, s.cursor) };
}
export function bufMoveLineEnd(s) {
    const cps = toCps(s.text);
    return { text: s.text, cursor: findLineEnd(cps, s.cursor) };
}
export function bufMoveBufferStart(s) {
    return { text: s.text, cursor: 0 };
}
export function bufMoveBufferEnd(s) {
    return { text: s.text, cursor: toCps(s.text).length };
}
/** ↑：上移一个逻辑行，保留列位（目标行短就贴行末） */
export function bufMoveUp(s) {
    const cps = toCps(s.text);
    const lineStart = findLineStart(cps, s.cursor);
    if (lineStart === 0)
        return s; // 已在首行
    const prevLineEnd = lineStart - 1; // 指向上一行末尾的 \n
    const prevLineStart = findLineStart(cps, prevLineEnd);
    const col = s.cursor - lineStart;
    const prevLineLen = prevLineEnd - prevLineStart;
    return { text: s.text, cursor: prevLineStart + Math.min(col, prevLineLen) };
}
/** ↓：下移一个逻辑行，保留列位 */
export function bufMoveDown(s) {
    const cps = toCps(s.text);
    const lineStart = findLineStart(cps, s.cursor);
    const lineEnd = findLineEnd(cps, s.cursor);
    if (lineEnd === cps.length)
        return s; // 已在末行
    const nextLineStart = lineEnd + 1;
    const nextLineEnd = findLineEnd(cps, nextLineStart);
    const col = s.cursor - lineStart;
    const nextLineLen = nextLineEnd - nextLineStart;
    return { text: s.text, cursor: nextLineStart + Math.min(col, nextLineLen) };
}
/**
 * 给定一个 BufferState，算出它的可视布局。
 * 渲染端拿到 lines + cursorRow/Col 就能把光标绘制在正确位置。
 */
export function layoutBuffer(s) {
    const cps = toCps(s.text);
    const cursor = clamp(cps, s.cursor);
    const lineStart = findLineStart(cps, cursor);
    // 把 cps 按 \n 分行（保持 code-point 视角，避免 surrogate pair 撕裂）
    const lines = [];
    let buf = [];
    let cursorRow = 0;
    let charCount = 0;
    for (const ch of cps) {
        if (ch === '\n') {
            lines.push(fromCps(buf));
            buf = [];
            if (charCount < lineStart)
                cursorRow++;
        }
        else {
            buf.push(ch);
        }
        charCount++;
    }
    lines.push(fromCps(buf));
    const cursorCol = cursor - lineStart;
    return { lines, cursorRow, cursorCol };
}
