// The MIT License (MIT)
//
// Copyright (C) 2026 Scott Duensing
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.

// textHelp.h -- Public API for the shared text editing infrastructure library
//
// Declares clipboard operations, multi-click detection, word boundary
// logic, cross-widget selection clearing, and the single-line text
// editing engine shared across widget DXEs (TextInput, Spinner,
// ComboBox, AnsiTerm, etc.).

#ifndef TEXT_HELP_H
#define TEXT_HELP_H

#include "../libdvx/dvxWgtP.h"

#define TEXT_INPUT_PAD        3

// ============================================================
// Cursor blink
// ============================================================

void wgtUpdateCursorBlink(void);

// ============================================================
// Selection management
// ============================================================

void clearOtherSelections(WidgetT *except);
void textEditClearWidgetRef(WidgetT *w);

// ============================================================
// Word / character helpers
// ============================================================

bool    isWordChar(char c);
int32_t textEditVisualCol(const char *buf, int32_t lineStart, int32_t off, int32_t tabW);
int32_t textEditVisualColToOff(const char *buf, int32_t len, int32_t lineStart, int32_t targetVC, int32_t tabW);
int32_t wordBoundaryLeft(const char *buf, int32_t pos);
int32_t wordBoundaryRight(const char *buf, int32_t len, int32_t pos);
int32_t wordEnd(const char *buf, int32_t len, int32_t pos);
int32_t wordStart(const char *buf, int32_t pos);

// ============================================================
// Undo buffer snapshot
// ============================================================

void textEditSaveUndo(char *buf, int32_t len, int32_t cursor, char *undoBuf, int32_t *pUndoLen, int32_t *pUndoCursor, int32_t bufSize);

// ============================================================
// Multi-line line-offset cache
// ============================================================
//
// Library-owned cache of line-start offsets and tab-expanded visual
// lengths for a text buffer. Widget owns the backing buffer; the
// cache is a derived index that must be invalidated (dirty) or
// incrementally updated whenever the buffer mutates.
//
// Contract: every mutator call on the cache takes the current buf
// and len so any fallback-to-rebuild path has what it needs. If the
// cache's tabWidth is changed via textEditLineCacheSetTabWidth it
// automatically dirties itself, since visual-length entries depend
// on tab stops.
typedef struct TextEditLineCacheT {
    int32_t *lineOffsets;     // [lineCount+1], last entry = buffer length sentinel
    int32_t *lineVisLens;     // [lineCount], tab-expanded visual column counts
    int32_t  lineOffsetCap;
    int32_t  lineVisLenCap;
    int32_t  cachedLines;     // -1 = dirty, otherwise >= 1
    int32_t  cachedMaxLL;     // -1 = unknown
    int32_t  tabWidth;        // tab stop width used to compute lineVisLens
} TextEditLineCacheT;

void    textEditLineCacheInit(TextEditLineCacheT *lc, int32_t tabWidth);
void    textEditLineCacheFree(TextEditLineCacheT *lc);
void    textEditLineCacheDirty(TextEditLineCacheT *lc);
void    textEditLineCacheSetTabWidth(TextEditLineCacheT *lc, int32_t tabWidth);
void    textEditLineCacheEnsure(TextEditLineCacheT *lc, const char *buf, int32_t len);
void    textEditLineCacheNotifyInsert(TextEditLineCacheT *lc, const char *buf, int32_t len, int32_t off, int32_t insertLen);
void    textEditLineCacheNotifyDelete(TextEditLineCacheT *lc, const char *buf, int32_t len, int32_t off, int32_t deleteLen);
int32_t textEditLineCount(TextEditLineCacheT *lc, const char *buf, int32_t len);
int32_t textEditLineLen(TextEditLineCacheT *lc, const char *buf, int32_t len, int32_t row);
int32_t textEditLineStart(TextEditLineCacheT *lc, const char *buf, int32_t len, int32_t row);
int32_t textEditMaxLineLen(TextEditLineCacheT *lc, const char *buf, int32_t len);
void    textEditOffToRowCol(TextEditLineCacheT *lc, const char *buf, int32_t len, int32_t off, int32_t *row, int32_t *col);
int32_t textEditRowColToOff(TextEditLineCacheT *lc, const char *buf, int32_t len, int32_t row, int32_t col);

// ============================================================
// Syntax coloring palette
// ============================================================
//
// Indices returned by a widget-supplied colorize callback. The palette
// has hard-coded defaults; any non-zero entry in a caller-supplied
// customColors[TEXT_SYNTAX_MAX] overrides the default.

#define TEXT_SYNTAX_DEFAULT     0
#define TEXT_SYNTAX_KEYWORD     1
#define TEXT_SYNTAX_STRING      2
#define TEXT_SYNTAX_COMMENT     3
#define TEXT_SYNTAX_NUMBER      4
#define TEXT_SYNTAX_OPERATOR    5
#define TEXT_SYNTAX_TYPE        6
#define TEXT_SYNTAX_MAX         7

uint32_t textEditSyntaxColor(const DisplayT *d, uint8_t idx, uint32_t defaultFg, const uint32_t *custom);
void     textEditDrawColorizedText(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int32_t x, int32_t y, const char *text, int32_t len, const uint8_t *syntaxColors, int32_t textOff, uint32_t defaultFg, uint32_t bg, const uint32_t *customColors);

// ============================================================
// Scroll adjustment
// ============================================================
//
// Adjust scrollRow/scrollCol so the cursor stays inside the visible
// window [visRows x visCols]. Visual column accounts for tab width.
void textEditEnsureVisible(TextEditLineCacheT *lc, const char *buf, int32_t len, int32_t cursorRow, int32_t cursorCol, int32_t tabW, int32_t visRows, int32_t visCols, int32_t *pScrollRow, int32_t *pScrollCol);

// ============================================================
// Multi-line paint
// ============================================================
//
// Renders the text content of a multi-line editor: per-line tab
// expansion, optional syntax coloring, selection highlight, cursor,
// and optional per-line background overrides. All widget chrome
// (border, gutter, scrollbars, focus rect) stays with the caller.
//
// The three scratch buffers (rawSyntax, expandBuf, syntaxBuf) are
// caller-owned so the caller controls the truncation policy for
// very long lines. Lines longer than scratchLen are drawn up to
// that limit and truncated cleanly.
typedef struct TextEditPaintHooksT {
    void   (*colorize)(const char *line, int32_t lineLen, uint8_t *colors, void *ctx);
    void    *colorizeCtx;
    uint32_t (*lineDecorator)(int32_t lineNum, uint32_t *gutterColor, void *ctx);
    void    *lineDecoratorCtx;
    uint8_t *rawSyntax;
    char    *expandBuf;
    uint8_t *syntaxBuf;
    int32_t  scratchLen;
    const uint32_t *customSyntaxColors;
} TextEditPaintHooksT;

void widgetTextEditMultiPaintArea(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors, TextEditLineCacheT *lc, const char *buf, int32_t len, int32_t textX, int32_t textY, int32_t innerW, int32_t visCols, int32_t visRows, int32_t scrollRow, int32_t scrollCol, int32_t cursorRow, int32_t cursorCol, int32_t selAnchor, int32_t selCursor, int32_t tabW, uint32_t fg, uint32_t bg, bool showCursor, const TextEditPaintHooksT *hooks);

// ============================================================
// Multi-line mouse click and drag
// ============================================================
//
// Mouse handling for the text content area. Caller is responsible
// for all chrome hit-tests (scrollbar, gutter) before calling.
//
// widgetTextEditMultiMouseClick places the cursor at the click
// position, handling single (cursor + drag anchor), double (select
// word), and triple (select line) clicks. Caller gets back updated
// cursor/desired/sel state.
//
// widgetTextEditMultiDragUpdateArea is called during a drag to
// auto-scroll the view and extend the selection.
void widgetTextEditMultiMouseClick(WidgetT *w, TextEditLineCacheT *lc, const char *buf, int32_t len, int32_t vx, int32_t vy, int32_t textX, int32_t textY, const BitmapFontT *font, int32_t scrollRow, int32_t scrollCol, int32_t tabW, int32_t *pCursorRow, int32_t *pCursorCol, int32_t *pDesiredCol, int32_t *pSelAnchor, int32_t *pSelCursor);
void widgetTextEditMultiDragUpdateArea(TextEditLineCacheT *lc, const char *buf, int32_t len, int32_t vx, int32_t vy, int32_t textX, int32_t textY, const BitmapFontT *font, int32_t *pScrollRow, int32_t scrollCol, int32_t visRows, int32_t tabW, int32_t *pCursorRow, int32_t *pCursorCol, int32_t *pDesiredCol, int32_t *pSelCursor);

// ============================================================
// Multi-line key handler
// ============================================================
//
// Options bundle: bundles the small set of mode flags the key
// handler needs to know about. Avoids a 20-argument entry point.

typedef struct TextEditMultiOptionsT {
    // Fields are ordered by alignment to minimize struct padding.
    int32_t tabWidth;
    bool    autoIndent;
    bool    captureTabs;
    bool    useTabChar;
    bool    readOnly;
} TextEditMultiOptionsT;

// Runs a single keystroke through the multi-line editing engine.
// Handles clipboard (Ctrl+A/C/V/X), undo (Ctrl+Z), navigation
// (arrows, Ctrl-arrows, Home/End, Ctrl-Home/Ctrl-End, PgUp/PgDn),
// editing (Enter with auto-indent, Backspace, Delete, Tab,
// printable characters), and shift-based selection extension.
//
// The caller owns buf/undoBuf storage; buf must have room for
// bufSize - 1 characters plus terminator. The library invalidates
// the cache when it knows edits span newlines; incremental
// single-byte updates go through the cache's notify paths.
//
// Fires w->onChange for edits that mutate the buffer. Calls
// wgtInvalidatePaint(w) before returning for any keystroke that
// visibly changes state.
void widgetTextEditMultiOnKey(WidgetT *w, int32_t key, int32_t mod, TextEditLineCacheT *lc, char *buf, int32_t bufSize, int32_t *pLen, int32_t *pCursorRow, int32_t *pCursorCol, int32_t *pDesiredCol, int32_t *pScrollRow, int32_t *pScrollCol, int32_t *pSelAnchor, int32_t *pSelCursor, char *undoBuf, int32_t *pUndoLen, int32_t *pUndoCursor, int32_t visRows, int32_t visCols, const TextEditMultiOptionsT *opts);

// ============================================================
// Text-grid scrollbars
// ============================================================
//
// Scrollbar helpers for widgets that scroll a text grid (lines x
// columns) rather than pixels. Unlike the general-purpose bevel
// scrollbars, these pass `total`/`visible`/`scroll` in logical
// units (lines or columns). Layout is fixed: arrow button at each
// end sized `thick` x `thick`, remainder is the thumb track.
//
// widgetTextScrollbarHitTest returns one of the TEXT_SB_HIT_*
// values. TEXT_SB_HIT_THUMB sets *pDragOff to the click offset
// from the thumb origin, for use in subsequent drag updates.
//
// widgetTextScrollbarDragToScroll converts a mouse coordinate
// during an active thumb drag to a clamped scroll value.

#define TEXT_SB_HIT_NONE       0
#define TEXT_SB_HIT_UP         1
#define TEXT_SB_HIT_DOWN       2
#define TEXT_SB_HIT_PAGE_UP    3
#define TEXT_SB_HIT_PAGE_DOWN  4
#define TEXT_SB_HIT_THUMB      5

void    widgetTextScrollbarDraw(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, bool vertical, int32_t x, int32_t y, int32_t thick, int32_t len, int32_t total, int32_t visible, int32_t scroll);
int32_t widgetTextScrollbarDragToScroll(bool vertical, int32_t mouseCoord, int32_t sbStart, int32_t thick, int32_t len, int32_t total, int32_t visible, int32_t dragOff);
int32_t widgetTextScrollbarHitTest(bool vertical, int32_t vx, int32_t vy, int32_t x, int32_t y, int32_t thick, int32_t len, int32_t total, int32_t visible, int32_t scroll, int32_t *pDragOff);

// ============================================================
// Multi-line editor state aggregate
// ============================================================
//
// Groups the per-editor state (buffer, cursor, selection, scroll,
// undo slot, line cache) into a single struct. Consumers embed this
// in their widget data; the library operates on the individual
// fields via pointer parameters (see widgetTextEditMulti* calls).
//
// Allocation policy: caller owns the buf/undoBuf storage. Use
// textEditMultiInit to allocate them (returns false if malloc
// fails) and textEditMultiFree to release.
typedef struct TextEditMultiT {
    char    *buf;
    int32_t  bufSize;
    int32_t  len;

    int32_t  cursorRow;
    int32_t  cursorCol;
    int32_t  desiredCol;

    int32_t  scrollRow;
    int32_t  scrollCol;

    int32_t  selAnchor;
    int32_t  selCursor;

    char    *undoBuf;
    int32_t  undoLen;
    int32_t  undoCursor;

    TextEditLineCacheT lines;
} TextEditMultiT;

bool textEditMultiInit(TextEditMultiT *te, int32_t maxLen, int32_t tabWidth);
void textEditMultiFree(TextEditMultiT *te);

// ============================================================
// Multi-line text operations
// ============================================================
//
// Pure text operations: no widget chrome, no scrollbar math (beyond
// visRows for centering). The caller invalidates paint / ensures
// visibility using its own conventions after these return.
//
// textEditFindNext: searches forward/backward from the current
// cursor position. If found, selects the match and moves the cursor
// to the match's start (forward) or end (backward). Returns true
// on match, false on no-match (no wrap-around).
//
// textEditReplaceAll: replaces every occurrence of needle with
// replacement. Records a single undo snapshot before any change.
// Returns the number of replacements made.
//
// textEditGoToLine: places cursor at the start of the given
// 1-based line, selects the entire line, and scrolls so the line
// sits ~1/4 from the top of the visible area (with clamping).
//
// textEditGetWordAtCursor: writes the word under the cursor into
// the caller's buffer. Returns the length written (0 if no word).
bool    textEditFindNext(TextEditLineCacheT *lc, const char *buf, int32_t len, const char *needle, bool caseSensitive, bool forward, int32_t *pCursorRow, int32_t *pCursorCol, int32_t *pDesiredCol, int32_t *pSelAnchor, int32_t *pSelCursor);
int32_t textEditGetWordAtCursor(TextEditLineCacheT *lc, const char *buf, int32_t len, int32_t cursorRow, int32_t cursorCol, char *out, int32_t outSize);
void    textEditGoToLine(TextEditLineCacheT *lc, const char *buf, int32_t len, int32_t line, int32_t visRows, int32_t *pCursorRow, int32_t *pCursorCol, int32_t *pDesiredCol, int32_t *pScrollRow, int32_t *pScrollCol, int32_t *pSelAnchor, int32_t *pSelCursor);
int32_t textEditReplaceAll(TextEditLineCacheT *lc, char *buf, int32_t bufSize, int32_t *pLen, const char *needle, const char *replacement, bool caseSensitive, int32_t *pCursorRow, int32_t *pCursorCol, int32_t *pDesiredCol, int32_t *pSelAnchor, int32_t *pSelCursor, char *undoBuf, int32_t *pUndoLen, int32_t *pUndoCursor);

// textEditReplaceSelectionMulti: if the current selection exactly
// matches needle, replace it (single undo snapshot) and leave the
// cursor positioned to continue searching in direction forward.
// Returns true only if a replacement was performed.
bool    textEditReplaceSelectionMulti(TextEditLineCacheT *lc, char *buf, int32_t bufSize, int32_t *pLen, const char *needle, const char *replacement, bool caseSensitive, bool forward, int32_t *pCursorRow, int32_t *pCursorCol, int32_t *pDesiredCol, int32_t *pSelAnchor, int32_t *pSelCursor, char *undoBuf, int32_t *pUndoLen, int32_t *pUndoCursor);

// ============================================================
// Single-line text editing engine
// ============================================================

void widgetTextEditDragUpdateLine(int32_t vx, int32_t leftEdge, int32_t maxChars, const BitmapFontT *font, int32_t len, int32_t *pCursorPos, int32_t *pScrollOff, int32_t *pSelEnd);
void widgetTextEditMouseClick(WidgetT *w, int32_t vx, int32_t vy, int32_t textLeftX, const BitmapFontT *font, const char *buf, int32_t len, int32_t scrollOff, int32_t *pCursorPos, int32_t *pSelStart, int32_t *pSelEnd, bool wordSelect, bool dragSelect);
void widgetTextEditOnKey(WidgetT *w, int32_t key, int32_t mod, char *buf, int32_t bufSize, int32_t *pLen, int32_t *pCursor, int32_t *pScrollOff, int32_t *pSelStart, int32_t *pSelEnd, char *undoBuf, int32_t *pUndoLen, int32_t *pUndoCursor, int32_t fieldWidth);
void widgetTextEditPaintLine(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors, int32_t textX, int32_t textY, const char *buf, int32_t visLen, int32_t scrollOff, int32_t cursorPos, int32_t selStart, int32_t selEnd, uint32_t fg, uint32_t bg, bool showCursor, int32_t cursorMinX, int32_t cursorMaxX);

#endif // TEXT_HELP_H
