import { useState, useCallback, useEffect, useMemo, useReducer } from "react";
import {
  toCodePoints,
  cpLen,
  cpSlice,
  stripUnsafeCharacters,
  getCachedStringWidth,
  isWordCharStrict,
  isWhitespace,
  isWordCharWithCombining,
  isDifferentScript
} from "../../utils/textUtils.js";
const findNextWordStartInLine = (line, col) => {
  const chars = toCodePoints(line);
  let i = col;
  if (i >= chars.length) return null;
  const currentChar = chars[i];
  if (isWordCharStrict(currentChar)) {
    while (i < chars.length && isWordCharWithCombining(chars[i])) {
      if (i + 1 < chars.length && isWordCharStrict(chars[i + 1]) && isDifferentScript(chars[i], chars[i + 1])) {
        i++;
        break;
      }
      i++;
    }
  } else if (!isWhitespace(currentChar)) {
    while (i < chars.length && !isWordCharStrict(chars[i]) && !isWhitespace(chars[i])) {
      i++;
    }
  }
  while (i < chars.length && isWhitespace(chars[i])) {
    i++;
  }
  return i < chars.length ? i : null;
};
const findPrevWordStartInLine = (line, col) => {
  const chars = toCodePoints(line);
  let i = col;
  if (i <= 0) return null;
  i--;
  while (i >= 0 && isWhitespace(chars[i])) {
    i--;
  }
  if (i < 0) return null;
  if (isWordCharStrict(chars[i])) {
    while (i >= 0 && isWordCharStrict(chars[i])) {
      if (i - 1 >= 0 && isWordCharStrict(chars[i - 1]) && isDifferentScript(chars[i], chars[i - 1])) {
        return i;
      }
      i--;
    }
    return i + 1;
  } else {
    while (i >= 0 && !isWordCharStrict(chars[i]) && !isWhitespace(chars[i])) {
      i--;
    }
    return i + 1;
  }
};
const findWordEndInLine = (line, col) => {
  const chars = toCodePoints(line);
  let i = col;
  const atEndOfWordChar = i < chars.length && isWordCharWithCombining(chars[i]) && (i + 1 >= chars.length || !isWordCharWithCombining(chars[i + 1]) || isWordCharStrict(chars[i]) && i + 1 < chars.length && isWordCharStrict(chars[i + 1]) && isDifferentScript(chars[i], chars[i + 1]));
  const atEndOfPunctuation = i < chars.length && !isWordCharWithCombining(chars[i]) && !isWhitespace(chars[i]) && (i + 1 >= chars.length || isWhitespace(chars[i + 1]) || isWordCharWithCombining(chars[i + 1]));
  if (atEndOfWordChar || atEndOfPunctuation) {
    i++;
    while (i < chars.length && isWhitespace(chars[i])) {
      i++;
    }
  }
  if (i < chars.length && !isWordCharWithCombining(chars[i])) {
    while (i < chars.length && isWhitespace(chars[i])) {
      i++;
    }
  }
  let foundWord = false;
  let lastBaseCharPos = -1;
  if (i < chars.length && isWordCharWithCombining(chars[i])) {
    while (i < chars.length && isWordCharWithCombining(chars[i])) {
      foundWord = true;
      if (isWordCharStrict(chars[i])) {
        lastBaseCharPos = i;
      }
      if (i + 1 < chars.length && isWordCharStrict(chars[i + 1]) && isDifferentScript(chars[i], chars[i + 1])) {
        i++;
        if (isWordCharStrict(chars[i - 1])) {
          lastBaseCharPos = i - 1;
        }
        break;
      }
      i++;
    }
  } else if (i < chars.length && !isWhitespace(chars[i])) {
    while (i < chars.length && !isWordCharStrict(chars[i]) && !isWhitespace(chars[i])) {
      foundWord = true;
      lastBaseCharPos = i;
      i++;
    }
  }
  if (foundWord && lastBaseCharPos >= col) {
    return lastBaseCharPos;
  }
  return null;
};
const segmenter = new Intl.Segmenter(void 0, { granularity: "word" });
function findPrevWordBoundary(line, cursorCol) {
  const codePoints = toCodePoints(line);
  const prefix = codePoints.slice(0, cursorCol).join("");
  const cursorIdx = prefix.length;
  let targetIdx = 0;
  for (const seg of segmenter.segment(line)) {
    if (seg.index >= cursorIdx) break;
    if (seg.isWordLike) {
      targetIdx = seg.index;
    }
  }
  return toCodePoints(line.slice(0, targetIdx)).length;
}
function findNextWordBoundary(line, cursorCol) {
  const codePoints = toCodePoints(line);
  const prefix = codePoints.slice(0, cursorCol).join("");
  const cursorIdx = prefix.length;
  let targetIdx = line.length;
  for (const seg of segmenter.segment(line)) {
    const segEnd = seg.index + seg.segment.length;
    if (segEnd > cursorIdx) {
      if (seg.isWordLike) {
        targetIdx = segEnd;
        break;
      }
    }
  }
  return toCodePoints(line.slice(0, targetIdx)).length;
}
const findNextWordAcrossLines = (lines, cursorRow, cursorCol, searchForWordStart) => {
  const currentLine = lines[cursorRow] || "";
  const colInCurrentLine = searchForWordStart ? findNextWordStartInLine(currentLine, cursorCol) : findWordEndInLine(currentLine, cursorCol);
  if (colInCurrentLine !== null) {
    return { row: cursorRow, col: colInCurrentLine };
  }
  for (let row = cursorRow + 1; row < lines.length; row++) {
    const line = lines[row] || "";
    const chars = toCodePoints(line);
    if (chars.length === 0) {
      let hasWordsInLaterLines = false;
      for (let laterRow = row + 1; laterRow < lines.length; laterRow++) {
        const laterLine = lines[laterRow] || "";
        const laterChars = toCodePoints(laterLine);
        let firstNonWhitespace2 = 0;
        while (firstNonWhitespace2 < laterChars.length && isWhitespace(laterChars[firstNonWhitespace2])) {
          firstNonWhitespace2++;
        }
        if (firstNonWhitespace2 < laterChars.length) {
          hasWordsInLaterLines = true;
          break;
        }
      }
      if (!hasWordsInLaterLines) {
        return { row, col: 0 };
      }
      continue;
    }
    let firstNonWhitespace = 0;
    while (firstNonWhitespace < chars.length && isWhitespace(chars[firstNonWhitespace])) {
      firstNonWhitespace++;
    }
    if (firstNonWhitespace < chars.length) {
      if (searchForWordStart) {
        return { row, col: firstNonWhitespace };
      } else {
        const endCol = findWordEndInLine(line, firstNonWhitespace);
        if (endCol !== null) {
          return { row, col: endCol };
        }
      }
    }
  }
  return null;
};
const findPrevWordAcrossLines = (lines, cursorRow, cursorCol) => {
  const currentLine = lines[cursorRow] || "";
  const colInCurrentLine = findPrevWordStartInLine(currentLine, cursorCol);
  if (colInCurrentLine !== null) {
    return { row: cursorRow, col: colInCurrentLine };
  }
  for (let row = cursorRow - 1; row >= 0; row--) {
    const line = lines[row] || "";
    const chars = toCodePoints(line);
    if (chars.length === 0) continue;
    let lastWordStart = chars.length;
    while (lastWordStart > 0 && isWhitespace(chars[lastWordStart - 1])) {
      lastWordStart--;
    }
    if (lastWordStart > 0) {
      const wordStart = findPrevWordStartInLine(line, lastWordStart);
      if (wordStart !== null) {
        return { row, col: wordStart };
      }
    }
  }
  return null;
};
const getPositionFromOffsets = (startOffset, endOffset, lines) => {
  let offset = 0;
  let startRow = 0;
  let startCol = 0;
  let endRow = 0;
  let endCol = 0;
  for (let i = 0; i < lines.length; i++) {
    const lineLength = lines[i].length + 1;
    if (offset + lineLength > startOffset) {
      startRow = i;
      startCol = startOffset - offset;
      break;
    }
    offset += lineLength;
  }
  offset = 0;
  for (let i = 0; i < lines.length; i++) {
    const lineLength = lines[i].length + (i < lines.length - 1 ? 1 : 0);
    if (offset + lineLength >= endOffset) {
      endRow = i;
      endCol = endOffset - offset;
      break;
    }
    offset += lineLength;
  }
  return { startRow, startCol, endRow, endCol };
};
const getLineRangeOffsets = (startRow, lineCount, lines) => {
  let startOffset = 0;
  for (let i = 0; i < startRow; i++) {
    startOffset += lines[i].length + 1;
  }
  let endOffset = startOffset;
  for (let i = 0; i < lineCount; i++) {
    const lineIndex = startRow + i;
    if (lineIndex < lines.length) {
      endOffset += lines[lineIndex].length;
      if (lineIndex < lines.length - 1) {
        endOffset += 1;
      }
    }
  }
  return { startOffset, endOffset };
};
const replaceRangeInternal = (state, startRow, startCol, endRow, endCol, text) => {
  const currentLine = (row) => state.lines[row] || "";
  const currentLineLen = (row) => cpLen(currentLine(row));
  const clamp2 = (value, min, max) => Math.min(Math.max(value, min), max);
  if (startRow > endRow || startRow === endRow && startCol > endCol || startRow < 0 || startCol < 0 || endRow >= state.lines.length || endRow < state.lines.length && endCol > currentLineLen(endRow)) {
    return state;
  }
  const newLines = [...state.lines];
  const sCol = clamp2(startCol, 0, currentLineLen(startRow));
  const eCol = clamp2(endCol, 0, currentLineLen(endRow));
  const prefix = cpSlice(currentLine(startRow), 0, sCol);
  const suffix = cpSlice(currentLine(endRow), eCol);
  const normalisedReplacement = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
  const replacementParts = normalisedReplacement.split("\n");
  const firstLine = prefix + replacementParts[0];
  if (replacementParts.length === 1) {
    newLines.splice(startRow, endRow - startRow + 1, firstLine + suffix);
  } else {
    const lastLine = replacementParts[replacementParts.length - 1] + suffix;
    const middleLines = replacementParts.slice(1, -1);
    newLines.splice(startRow, endRow - startRow + 1, firstLine, ...middleLines, lastLine);
  }
  const finalCursorRow = startRow + replacementParts.length - 1;
  const finalCursorCol = (replacementParts.length > 1 ? 0 : sCol) + cpLen(replacementParts[replacementParts.length - 1]);
  return {
    ...state,
    lines: newLines,
    cursorRow: Math.min(Math.max(finalCursorRow, 0), newLines.length - 1),
    cursorCol: Math.max(0, Math.min(finalCursorCol, cpLen(newLines[finalCursorRow] || ""))),
    preferredCol: null
  };
};
function clamp(v, min, max) {
  return v < min ? min : v > max ? max : v;
}
function calculateInitialCursorPosition(initialLines, offset) {
  let remainingChars = offset;
  let row = 0;
  while (row < initialLines.length) {
    const lineLength = cpLen(initialLines[row]);
    const totalCharsInLineAndNewline = lineLength + (row < initialLines.length - 1 ? 1 : 0);
    if (remainingChars <= lineLength) {
      return [row, remainingChars];
    }
    remainingChars -= totalCharsInLineAndNewline;
    row++;
  }
  if (initialLines.length > 0) {
    const lastRow = initialLines.length - 1;
    return [lastRow, cpLen(initialLines[lastRow])];
  }
  return [0, 0];
}
function offsetToLogicalPos(text, offset) {
  let row = 0;
  let col = 0;
  let currentOffset = 0;
  if (offset === 0) return [0, 0];
  const lines = text.split("\n");
  for (let i = 0; i < lines.length; i++) {
    const line = lines[i];
    const lineLength = cpLen(line);
    const lineLengthWithNewline = lineLength + (i < lines.length - 1 ? 1 : 0);
    if (offset <= currentOffset + lineLength) {
      row = i;
      col = offset - currentOffset;
      return [row, col];
    } else if (offset <= currentOffset + lineLengthWithNewline) {
      row = i;
      col = lineLength;
      if (offset === currentOffset + lineLengthWithNewline && i < lines.length - 1) {
        return [i + 1, 0];
      }
      return [row, col];
    }
    currentOffset += lineLengthWithNewline;
  }
  if (lines.length > 0) {
    row = lines.length - 1;
    col = cpLen(lines[row]);
  } else {
    row = 0;
    col = 0;
  }
  return [row, col];
}
function logicalPosToOffset(lines, row, col) {
  let offset = 0;
  const actualRow = Math.min(row, lines.length - 1);
  for (let i = 0; i < actualRow; i++) {
    offset += cpLen(lines[i]) + 1;
  }
  if (actualRow >= 0 && actualRow < lines.length) {
    offset += Math.min(col, cpLen(lines[actualRow]));
  }
  return offset;
}
function calculateLayout(logicalLines, viewportWidth) {
  const visualLines = [];
  const logicalToVisualMap = [];
  const visualToLogicalMap = [];
  logicalLines.forEach((logLine, logIndex) => {
    logicalToVisualMap[logIndex] = [];
    if (logLine.length === 0) {
      logicalToVisualMap[logIndex].push([visualLines.length, 0]);
      visualToLogicalMap.push([logIndex, 0]);
      visualLines.push("");
    } else {
      let currentPosInLogLine = 0;
      const codePointsInLogLine = toCodePoints(logLine);
      while (currentPosInLogLine < codePointsInLogLine.length) {
        let currentChunk = "";
        let currentChunkVisualWidth = 0;
        let numCodePointsInChunk = 0;
        let lastWordBreakPoint = -1;
        let numCodePointsAtLastWordBreak = 0;
        for (let i = currentPosInLogLine; i < codePointsInLogLine.length; i++) {
          const char = codePointsInLogLine[i];
          const charVisualWidth = getCachedStringWidth(char);
          if (currentChunkVisualWidth + charVisualWidth > viewportWidth) {
            if (lastWordBreakPoint !== -1 && numCodePointsAtLastWordBreak > 0 && currentPosInLogLine + numCodePointsAtLastWordBreak < i) {
              currentChunk = codePointsInLogLine.slice(
                currentPosInLogLine,
                currentPosInLogLine + numCodePointsAtLastWordBreak
              ).join("");
              numCodePointsInChunk = numCodePointsAtLastWordBreak;
            } else {
              if (numCodePointsInChunk === 0 && charVisualWidth > viewportWidth) {
                currentChunk = char;
                numCodePointsInChunk = 1;
              } else if (numCodePointsInChunk === 0 && charVisualWidth <= viewportWidth) {
              }
            }
            break;
          }
          currentChunk += char;
          currentChunkVisualWidth += charVisualWidth;
          numCodePointsInChunk++;
          if (char === " ") {
            lastWordBreakPoint = i;
            numCodePointsAtLastWordBreak = numCodePointsInChunk - 1;
          }
        }
        if (numCodePointsInChunk === 0 && currentPosInLogLine < codePointsInLogLine.length) {
          const firstChar = codePointsInLogLine[currentPosInLogLine];
          currentChunk = firstChar;
          numCodePointsInChunk = 1;
        }
        if (numCodePointsInChunk === 0 && currentPosInLogLine < codePointsInLogLine.length) {
          currentChunk = codePointsInLogLine[currentPosInLogLine];
          numCodePointsInChunk = 1;
        }
        logicalToVisualMap[logIndex].push([visualLines.length, currentPosInLogLine]);
        visualToLogicalMap.push([logIndex, currentPosInLogLine]);
        visualLines.push(currentChunk);
        const logicalStartOfThisChunk = currentPosInLogLine;
        currentPosInLogLine += numCodePointsInChunk;
        if (logicalStartOfThisChunk + numCodePointsInChunk < codePointsInLogLine.length && currentPosInLogLine < codePointsInLogLine.length && // Redundant if previous is true, but safe
        codePointsInLogLine[currentPosInLogLine] === " ") {
          currentPosInLogLine++;
        }
      }
    }
  });
  if (logicalLines.length === 0 || logicalLines.length === 1 && logicalLines[0] === "") {
    if (visualLines.length === 0) {
      visualLines.push("");
      if (!logicalToVisualMap[0]) logicalToVisualMap[0] = [];
      logicalToVisualMap[0].push([0, 0]);
      visualToLogicalMap.push([0, 0]);
    }
  }
  return {
    visualLines,
    logicalToVisualMap,
    visualToLogicalMap
  };
}
function calculateVisualCursorFromLayout(layout, logicalCursor) {
  const { logicalToVisualMap, visualLines } = layout;
  const [logicalRow, logicalCol] = logicalCursor;
  const segmentsForLogicalLine = logicalToVisualMap[logicalRow];
  if (!segmentsForLogicalLine || segmentsForLogicalLine.length === 0) {
    return [0, 0];
  }
  let targetSegmentIndex = segmentsForLogicalLine.findIndex(([, startColInLogical2], index) => {
    const nextStartColInLogical = index + 1 < segmentsForLogicalLine.length ? segmentsForLogicalLine[index + 1][1] : Infinity;
    return logicalCol >= startColInLogical2 && logicalCol < nextStartColInLogical;
  });
  if (targetSegmentIndex === -1) {
    if (logicalCol === 0) {
      targetSegmentIndex = 0;
    } else {
      targetSegmentIndex = segmentsForLogicalLine.length - 1;
    }
  }
  const [visualRow, startColInLogical] = segmentsForLogicalLine[targetSegmentIndex];
  const visualCol = logicalCol - startColInLogical;
  const clampedVisualCol = Math.min(visualCol, cpLen(visualLines[visualRow] ?? ""));
  return [visualRow, clampedVisualCol];
}
const historyLimit = 100;
const pushUndo = (currentState) => {
  const snapshot = {
    lines: [...currentState.lines],
    cursorRow: currentState.cursorRow,
    cursorCol: currentState.cursorCol
  };
  const newStack = [...currentState.undoStack, snapshot];
  if (newStack.length > historyLimit) {
    newStack.shift();
  }
  return { ...currentState, undoStack: newStack, redoStack: [] };
};
function textBufferReducerLogic(state, action, options = {}) {
  const pushUndoLocal = pushUndo;
  const currentLine = (r) => state.lines[r] ?? "";
  const currentLineLen = (r) => cpLen(currentLine(r));
  switch (action.type) {
    case "set_text": {
      let nextState = state;
      if (action.pushToUndo !== false) {
        nextState = pushUndoLocal(state);
      }
      const newContentLines = action.payload.replace(/\r\n?/g, "\n").split("\n");
      const lines = newContentLines.length === 0 ? [""] : newContentLines;
      const lastNewLineIndex = lines.length - 1;
      return {
        ...nextState,
        lines,
        cursorRow: lastNewLineIndex,
        cursorCol: cpLen(lines[lastNewLineIndex] ?? ""),
        preferredCol: null
      };
    }
    case "insert": {
      let payload = action.payload;
      if (options.singleLine) {
        payload = payload.replace(/[\r\n]/g, "");
      }
      if (options.inputFilter) {
        payload = options.inputFilter(payload);
      }
      if (payload.length === 0) {
        return state;
      }
      const nextState = pushUndoLocal(state);
      const newLines = [...nextState.lines];
      let newCursorRow = nextState.cursorRow;
      let newCursorCol = nextState.cursorCol;
      const currentLine2 = (r) => newLines[r] ?? "";
      const str = stripUnsafeCharacters(payload.replace(/\r\n/g, "\n").replace(/\r/g, "\n"));
      const parts = str.split("\n");
      const lineContent = currentLine2(newCursorRow);
      const before = cpSlice(lineContent, 0, newCursorCol);
      const after = cpSlice(lineContent, newCursorCol);
      if (parts.length > 1) {
        newLines[newCursorRow] = before + parts[0];
        const remainingParts = parts.slice(1);
        const lastPartOriginal = remainingParts.pop() ?? "";
        newLines.splice(newCursorRow + 1, 0, ...remainingParts);
        newLines.splice(newCursorRow + parts.length - 1, 0, lastPartOriginal + after);
        newCursorRow = newCursorRow + parts.length - 1;
        newCursorCol = cpLen(lastPartOriginal);
      } else {
        newLines[newCursorRow] = before + parts[0] + after;
        newCursorCol = cpLen(before) + cpLen(parts[0]);
      }
      return {
        ...nextState,
        lines: newLines,
        cursorRow: newCursorRow,
        cursorCol: newCursorCol,
        preferredCol: null
      };
    }
    case "backspace": {
      if (state.cursorCol === 0 && state.cursorRow === 0) return state;
      const nextState = pushUndoLocal(state);
      const newLines = [...nextState.lines];
      let newCursorRow = nextState.cursorRow;
      let newCursorCol = nextState.cursorCol;
      const currentLine2 = (r) => newLines[r] ?? "";
      if (newCursorCol > 0) {
        const lineContent = currentLine2(newCursorRow);
        newLines[newCursorRow] = cpSlice(lineContent, 0, newCursorCol - 1) + cpSlice(lineContent, newCursorCol);
        newCursorCol--;
      } else if (newCursorRow > 0) {
        const prevLineContent = currentLine2(newCursorRow - 1);
        const currentLineContentVal = currentLine2(newCursorRow);
        const newCol = cpLen(prevLineContent);
        newLines[newCursorRow - 1] = prevLineContent + currentLineContentVal;
        newLines.splice(newCursorRow, 1);
        newCursorRow--;
        newCursorCol = newCol;
      }
      return {
        ...nextState,
        lines: newLines,
        cursorRow: newCursorRow,
        cursorCol: newCursorCol,
        preferredCol: null
      };
    }
    case "set_viewport": {
      const { width, height } = action.payload;
      if (width === state.viewportWidth && height === state.viewportHeight) {
        return state;
      }
      return {
        ...state,
        viewportWidth: width,
        viewportHeight: height
      };
    }
    case "move": {
      const { dir } = action.payload;
      const { cursorRow, cursorCol, lines, visualLayout, preferredCol } = state;
      if (dir === "left" || dir === "right" || dir === "up" || dir === "down" || dir === "home" || dir === "end") {
        const visualCursor = calculateVisualCursorFromLayout(visualLayout, [
          cursorRow,
          cursorCol
        ]);
        const { visualLines, visualToLogicalMap } = visualLayout;
        let newVisualRow = visualCursor[0];
        let newVisualCol = visualCursor[1];
        let newPreferredCol = preferredCol;
        const currentVisLineLen = cpLen(visualLines[newVisualRow] ?? "");
        switch (dir) {
          case "left":
            newPreferredCol = null;
            if (newVisualCol > 0) {
              newVisualCol--;
            } else if (newVisualRow > 0) {
              newVisualRow--;
              newVisualCol = cpLen(visualLines[newVisualRow] ?? "");
            }
            break;
          case "right":
            newPreferredCol = null;
            if (newVisualCol < currentVisLineLen) {
              newVisualCol++;
            } else if (newVisualRow < visualLines.length - 1) {
              newVisualRow++;
              newVisualCol = 0;
            }
            break;
          case "up":
            if (newVisualRow > 0) {
              if (newPreferredCol === null) newPreferredCol = newVisualCol;
              newVisualRow--;
              newVisualCol = clamp(
                newPreferredCol,
                0,
                cpLen(visualLines[newVisualRow] ?? "")
              );
            }
            break;
          case "down":
            if (newVisualRow < visualLines.length - 1) {
              if (newPreferredCol === null) newPreferredCol = newVisualCol;
              newVisualRow++;
              newVisualCol = clamp(
                newPreferredCol,
                0,
                cpLen(visualLines[newVisualRow] ?? "")
              );
            }
            break;
          case "home":
            newPreferredCol = null;
            newVisualCol = 0;
            break;
          case "end":
            newPreferredCol = null;
            newVisualCol = currentVisLineLen;
            break;
          default:
            return state;
        }
        if (visualToLogicalMap[newVisualRow]) {
          const [logRow, logStartCol] = visualToLogicalMap[newVisualRow];
          return {
            ...state,
            cursorRow: logRow,
            cursorCol: clamp(logStartCol + newVisualCol, 0, cpLen(lines[logRow] ?? "")),
            preferredCol: newPreferredCol
          };
        }
        return state;
      }
      switch (dir) {
        case "wordLeft": {
          if (cursorCol === 0 && cursorRow === 0) return state;
          let newCursorRow = cursorRow;
          let newCursorCol = cursorCol;
          if (cursorCol === 0) {
            newCursorRow--;
            newCursorCol = cpLen(lines[newCursorRow] ?? "");
          } else {
            const lineContent = lines[cursorRow] ?? "";
            newCursorCol = findPrevWordBoundary(lineContent, cursorCol);
          }
          return {
            ...state,
            cursorRow: newCursorRow,
            cursorCol: newCursorCol,
            preferredCol: null
          };
        }
        case "wordRight": {
          const lineContent = lines[cursorRow] ?? "";
          if (cursorRow === lines.length - 1 && cursorCol === cpLen(lineContent)) {
            return state;
          }
          let newCursorRow = cursorRow;
          let newCursorCol = cursorCol;
          const lineLen = cpLen(lineContent);
          if (cursorCol >= lineLen) {
            newCursorRow++;
            newCursorCol = 0;
          } else {
            newCursorCol = findNextWordBoundary(lineContent, cursorCol);
          }
          return {
            ...state,
            cursorRow: newCursorRow,
            cursorCol: newCursorCol,
            preferredCol: null
          };
        }
        default:
          return state;
      }
    }
    case "set_cursor": {
      return {
        ...state,
        ...action.payload
      };
    }
    case "delete": {
      const { cursorRow, cursorCol, lines } = state;
      const lineContent = currentLine(cursorRow);
      if (cursorCol < currentLineLen(cursorRow)) {
        const nextState = pushUndoLocal(state);
        const newLines = [...nextState.lines];
        newLines[cursorRow] = cpSlice(lineContent, 0, cursorCol) + cpSlice(lineContent, cursorCol + 1);
        return {
          ...nextState,
          lines: newLines,
          preferredCol: null
        };
      } else if (cursorRow < lines.length - 1) {
        const nextState = pushUndoLocal(state);
        const nextLineContent = currentLine(cursorRow + 1);
        const newLines = [...nextState.lines];
        newLines[cursorRow] = lineContent + nextLineContent;
        newLines.splice(cursorRow + 1, 1);
        return {
          ...nextState,
          lines: newLines,
          preferredCol: null
        };
      }
      return state;
    }
    case "delete_word_left": {
      const { cursorRow, cursorCol } = state;
      if (cursorCol === 0 && cursorRow === 0) return state;
      const nextState = pushUndoLocal(state);
      const newLines = [...nextState.lines];
      let newCursorRow = cursorRow;
      let newCursorCol = cursorCol;
      if (newCursorCol > 0) {
        const lineContent = currentLine(newCursorRow);
        const prevWordStart = findPrevWordStartInLine(lineContent, newCursorCol);
        const start = prevWordStart === null ? 0 : prevWordStart;
        newLines[newCursorRow] = cpSlice(lineContent, 0, start) + cpSlice(lineContent, newCursorCol);
        newCursorCol = start;
      } else {
        const prevLineContent = currentLine(cursorRow - 1);
        const currentLineContentVal = currentLine(cursorRow);
        const newCol = cpLen(prevLineContent);
        newLines[cursorRow - 1] = prevLineContent + currentLineContentVal;
        newLines.splice(cursorRow, 1);
        newCursorRow--;
        newCursorCol = newCol;
      }
      return {
        ...nextState,
        lines: newLines,
        cursorRow: newCursorRow,
        cursorCol: newCursorCol,
        preferredCol: null
      };
    }
    case "delete_word_right": {
      const { cursorRow, cursorCol, lines } = state;
      const lineContent = currentLine(cursorRow);
      const lineLen = cpLen(lineContent);
      if (cursorCol >= lineLen && cursorRow === lines.length - 1) {
        return state;
      }
      const nextState = pushUndoLocal(state);
      const newLines = [...nextState.lines];
      if (cursorCol >= lineLen) {
        const nextLineContent = currentLine(cursorRow + 1);
        newLines[cursorRow] = lineContent + nextLineContent;
        newLines.splice(cursorRow + 1, 1);
      } else {
        const nextWordStart = findNextWordStartInLine(lineContent, cursorCol);
        const end = nextWordStart === null ? lineLen : nextWordStart;
        newLines[cursorRow] = cpSlice(lineContent, 0, cursorCol) + cpSlice(lineContent, end);
      }
      return {
        ...nextState,
        lines: newLines,
        preferredCol: null
      };
    }
    case "kill_line_right": {
      const { cursorRow, cursorCol, lines } = state;
      const lineContent = currentLine(cursorRow);
      if (cursorCol < currentLineLen(cursorRow)) {
        const nextState = pushUndoLocal(state);
        const newLines = [...nextState.lines];
        newLines[cursorRow] = cpSlice(lineContent, 0, cursorCol);
        return {
          ...nextState,
          lines: newLines
        };
      } else if (cursorRow < lines.length - 1) {
        const nextState = pushUndoLocal(state);
        const nextLineContent = currentLine(cursorRow + 1);
        const newLines = [...nextState.lines];
        newLines[cursorRow] = lineContent + nextLineContent;
        newLines.splice(cursorRow + 1, 1);
        return {
          ...nextState,
          lines: newLines,
          preferredCol: null
        };
      }
      return state;
    }
    case "kill_line_left": {
      const { cursorRow, cursorCol } = state;
      if (cursorCol > 0) {
        const nextState = pushUndoLocal(state);
        const lineContent = currentLine(cursorRow);
        const newLines = [...nextState.lines];
        newLines[cursorRow] = cpSlice(lineContent, cursorCol);
        return {
          ...nextState,
          lines: newLines,
          cursorCol: 0,
          preferredCol: null
        };
      }
      return state;
    }
    case "undo": {
      const stateToRestore = state.undoStack[state.undoStack.length - 1];
      if (!stateToRestore) return state;
      const currentSnapshot = {
        lines: [...state.lines],
        cursorRow: state.cursorRow,
        cursorCol: state.cursorCol
      };
      return {
        ...state,
        ...stateToRestore,
        undoStack: state.undoStack.slice(0, -1),
        redoStack: [...state.redoStack, currentSnapshot]
      };
    }
    case "redo": {
      const stateToRestore = state.redoStack[state.redoStack.length - 1];
      if (!stateToRestore) return state;
      const currentSnapshot = {
        lines: [...state.lines],
        cursorRow: state.cursorRow,
        cursorCol: state.cursorCol
      };
      return {
        ...state,
        ...stateToRestore,
        redoStack: state.redoStack.slice(0, -1),
        undoStack: [...state.undoStack, currentSnapshot]
      };
    }
    case "replace_range": {
      const { startRow, startCol, endRow, endCol, text } = action.payload;
      const nextState = pushUndoLocal(state);
      return replaceRangeInternal(nextState, startRow, startCol, endRow, endCol, text);
    }
    case "move_to_offset": {
      const { offset } = action.payload;
      const [newRow, newCol] = offsetToLogicalPos(state.lines.join("\n"), offset);
      return {
        ...state,
        cursorRow: newRow,
        cursorCol: newCol,
        preferredCol: null
      };
    }
    case "create_undo_snapshot": {
      return pushUndoLocal(state);
    }
    default:
      return state;
  }
}
function textBufferReducer(state, action, options = {}) {
  const newState = textBufferReducerLogic(state, action, options);
  if (newState.lines !== state.lines || newState.viewportWidth !== state.viewportWidth) {
    return {
      ...newState,
      visualLayout: calculateLayout(newState.lines, newState.viewportWidth)
    };
  }
  return newState;
}
function useTextBuffer({
  initialText = "",
  initialCursorOffset = 0,
  viewport,
  onChange,
  inputFilter,
  singleLine = false
}) {
  const initialState = useMemo(() => {
    const lines2 = initialText.split("\n");
    const [initialCursorRow, initialCursorCol] = calculateInitialCursorPosition(
      lines2.length === 0 ? [""] : lines2,
      initialCursorOffset
    );
    const visualLayout2 = calculateLayout(lines2.length === 0 ? [""] : lines2, viewport.width);
    return {
      lines: lines2.length === 0 ? [""] : lines2,
      cursorRow: initialCursorRow,
      cursorCol: initialCursorCol,
      preferredCol: null,
      undoStack: [],
      redoStack: [],
      viewportWidth: viewport.width,
      viewportHeight: viewport.height,
      visualLayout: visualLayout2
    };
  }, [initialText, initialCursorOffset, viewport.width, viewport.height]);
  const [state, dispatch] = useReducer(
    (s, a) => textBufferReducer(s, a, { inputFilter, singleLine }),
    initialState
  );
  const { lines, cursorRow, cursorCol, preferredCol, visualLayout } = state;
  const text = useMemo(() => lines.join("\n"), [lines]);
  const visualCursor = useMemo(
    () => calculateVisualCursorFromLayout(visualLayout, [cursorRow, cursorCol]),
    [visualLayout, cursorRow, cursorCol]
  );
  const { visualLines, visualToLogicalMap } = visualLayout;
  const [visualScrollRow, setVisualScrollRow] = useState(0);
  useEffect(() => {
    if (onChange) {
      onChange(text);
    }
  }, [text, onChange]);
  useEffect(() => {
    dispatch({
      type: "set_viewport",
      payload: { width: viewport.width, height: viewport.height }
    });
  }, [viewport.width, viewport.height]);
  useEffect(() => {
    const { height } = viewport;
    const totalVisualLines = visualLines.length;
    const maxScrollStart = Math.max(0, totalVisualLines - height);
    let newVisualScrollRow = visualScrollRow;
    if (visualCursor[0] < visualScrollRow) {
      newVisualScrollRow = visualCursor[0];
    } else if (visualCursor[0] >= visualScrollRow + height) {
      newVisualScrollRow = visualCursor[0] - height + 1;
    }
    newVisualScrollRow = clamp(newVisualScrollRow, 0, maxScrollStart);
    if (newVisualScrollRow !== visualScrollRow) {
      setVisualScrollRow(newVisualScrollRow);
    }
  }, [visualCursor, visualScrollRow, viewport, visualLines.length]);
  const insert = useCallback(
    (ch, { paste: _paste = false } = {}) => {
      if (!singleLine && /[\n\r]/.test(ch)) {
        dispatch({ type: "insert", payload: ch });
        return;
      }
      let currentText = "";
      for (const char of toCodePoints(ch)) {
        if (char.codePointAt(0) === 127) {
          if (currentText.length > 0) {
            dispatch({ type: "insert", payload: currentText });
            currentText = "";
          }
          dispatch({ type: "backspace" });
        } else {
          currentText += char;
        }
      }
      if (currentText.length > 0) {
        dispatch({ type: "insert", payload: currentText });
      }
    },
    [singleLine]
  );
  const newline = useCallback(() => {
    if (singleLine) {
      return;
    }
    dispatch({ type: "insert", payload: "\n" });
  }, [singleLine]);
  const backspace = useCallback(() => {
    dispatch({ type: "backspace" });
  }, []);
  const del = useCallback(() => {
    dispatch({ type: "delete" });
  }, []);
  const move = useCallback(
    (dir) => {
      dispatch({ type: "move", payload: { dir } });
    },
    [dispatch]
  );
  const undo = useCallback(() => {
    dispatch({ type: "undo" });
  }, []);
  const redo = useCallback(() => {
    dispatch({ type: "redo" });
  }, []);
  const setText = useCallback((newText) => {
    dispatch({ type: "set_text", payload: newText });
  }, []);
  const deleteWordLeft = useCallback(() => {
    dispatch({ type: "delete_word_left" });
  }, []);
  const deleteWordRight = useCallback(() => {
    dispatch({ type: "delete_word_right" });
  }, []);
  const killLineRight = useCallback(() => {
    dispatch({ type: "kill_line_right" });
  }, []);
  const killLineLeft = useCallback(() => {
    dispatch({ type: "kill_line_left" });
  }, []);
  const setViewport = useCallback((width, height) => {
    dispatch({ type: "set_viewport", payload: { width, height } });
  }, []);
  const handleInput = useCallback(
    (key) => {
      const { sequence: input } = key;
      if (key.paste) {
        insert(input, { paste: key.paste });
        return;
      }
      if (!singleLine && (key.name === "return" || input === "\r" || input === "\n" || input === "\\r"))
        newline();
      else if (key.name === "left" && !key.meta && !key.ctrl) move("left");
      else if (key.ctrl && key.name === "b") move("left");
      else if (key.name === "right" && !key.meta && !key.ctrl) move("right");
      else if (key.ctrl && key.name === "f") move("right");
      else if (key.name === "up") move("up");
      else if (key.name === "down") move("down");
      else if ((key.ctrl || key.meta) && key.name === "left") move("wordLeft");
      else if (key.meta && key.name === "b") move("wordLeft");
      else if ((key.ctrl || key.meta) && key.name === "right") move("wordRight");
      else if (key.meta && key.name === "f") move("wordRight");
      else if (key.name === "home") move("home");
      else if (key.ctrl && key.name === "a") move("home");
      else if (key.name === "end") move("end");
      else if (key.ctrl && key.name === "e") move("end");
      else if (key.ctrl && key.name === "w") deleteWordLeft();
      else if ((key.meta || key.ctrl) && (key.name === "backspace" || input === "\x7F"))
        deleteWordLeft();
      else if ((key.meta || key.ctrl) && key.name === "delete") deleteWordRight();
      else if (key.name === "backspace" || input === "\x7F" || key.ctrl && key.name === "h")
        backspace();
      else if (key.name === "delete" || key.ctrl && key.name === "d") del();
      else if (key.ctrl && !key.shift && key.name === "z") undo();
      else if (key.ctrl && key.shift && key.name === "z") redo();
      else if (key.insertable) {
        insert(input, { paste: key.paste });
      }
    },
    [
      newline,
      move,
      deleteWordLeft,
      deleteWordRight,
      backspace,
      del,
      insert,
      undo,
      redo,
      singleLine
    ]
  );
  const renderedVisualLines = useMemo(
    () => visualLines.slice(visualScrollRow, visualScrollRow + viewport.height),
    [visualLines, visualScrollRow, viewport.height]
  );
  const replaceRange = useCallback(
    (startRow, startCol, endRow, endCol, text2) => {
      dispatch({
        type: "replace_range",
        payload: { startRow, startCol, endRow, endCol, text: text2 }
      });
    },
    []
  );
  const replaceRangeByOffset = useCallback(
    (startOffset, endOffset, replacementText) => {
      const [startRow, startCol] = offsetToLogicalPos(text, startOffset);
      const [endRow, endCol] = offsetToLogicalPos(text, endOffset);
      replaceRange(startRow, startCol, endRow, endCol, replacementText);
    },
    [text, replaceRange]
  );
  const moveToOffset = useCallback((offset) => {
    dispatch({ type: "move_to_offset", payload: { offset } });
  }, []);
  const moveToVisualPosition = useCallback(
    (visRow, visCol) => {
      const { visualLines: visualLines2, visualToLogicalMap: visualToLogicalMap2 } = visualLayout;
      const clampedVisRow = Math.max(0, Math.min(visRow, visualLines2.length - 1));
      const visualLine = visualLines2[clampedVisRow] || "";
      if (visualToLogicalMap2[clampedVisRow]) {
        const [logRow, logStartCol] = visualToLogicalMap2[clampedVisRow];
        const codePoints = toCodePoints(visualLine);
        let currentVisX = 0;
        let charOffset = 0;
        for (const char of codePoints) {
          const charWidth = getCachedStringWidth(char);
          if (visCol < currentVisX + charWidth) {
            if (charWidth > 1 && visCol >= currentVisX + charWidth / 2) {
              charOffset++;
            }
            break;
          }
          currentVisX += charWidth;
          charOffset++;
        }
        charOffset = Math.min(charOffset, codePoints.length);
        const newCursorRow = logRow;
        const newCursorCol = logStartCol + charOffset;
        dispatch({
          type: "set_cursor",
          payload: {
            cursorRow: newCursorRow,
            cursorCol: newCursorCol,
            preferredCol: charOffset
          }
        });
      }
    },
    [visualLayout]
  );
  const getOffset = useCallback(
    () => logicalPosToOffset(lines, cursorRow, cursorCol),
    [lines, cursorRow, cursorCol]
  );
  const returnValue = useMemo(
    () => ({
      lines,
      text,
      cursor: [cursorRow, cursorCol],
      preferredCol,
      allVisualLines: visualLines,
      viewportVisualLines: renderedVisualLines,
      visualCursor,
      visualScrollRow,
      visualToLogicalMap,
      setText,
      insert,
      newline,
      backspace,
      del,
      move,
      undo,
      redo,
      replaceRange,
      replaceRangeByOffset,
      moveToOffset,
      getOffset,
      moveToVisualPosition,
      deleteWordLeft,
      deleteWordRight,
      killLineRight,
      killLineLeft,
      handleInput,
      setViewport
    }),
    [
      lines,
      text,
      cursorRow,
      cursorCol,
      preferredCol,
      visualLines,
      renderedVisualLines,
      visualCursor,
      visualScrollRow,
      visualToLogicalMap,
      setText,
      insert,
      newline,
      backspace,
      del,
      move,
      undo,
      redo,
      replaceRange,
      replaceRangeByOffset,
      moveToOffset,
      getOffset,
      moveToVisualPosition,
      deleteWordLeft,
      deleteWordRight,
      killLineRight,
      killLineLeft,
      handleInput,
      setViewport
    ]
  );
  return returnValue;
}
export {
  findNextWordAcrossLines,
  findNextWordStartInLine,
  findPrevWordAcrossLines,
  findPrevWordStartInLine,
  findWordEndInLine,
  getLineRangeOffsets,
  getPositionFromOffsets,
  logicalPosToOffset,
  offsetToLogicalPos,
  pushUndo,
  replaceRangeInternal,
  textBufferReducer,
  useTextBuffer
};
