#ifndef HUSH_EDITOR_H
#define HUSH_EDITOR_H

#include "document.h"
#include "layout_cache.h"
#include "undo.h"
#include "fonts.h"
#include "clay.h"
#include <stdbool.h>

typedef struct {
    Document doc;
    int cursorBlock;
    int cursorOffset;
    float preferredX; /* sticky x used across consecutive up/down moves; <0 means "recompute" */
    char *filePath;   /* owned, may be NULL if the editor was started without a file */
    bool dirty;
    UndoManager undo;
    int selAnchorBlock, selAnchorOffset; /* selection anchor; anchor == cursor means "no selection" */
} EditorState;

void editor_init(EditorState *ed);
void editor_free(EditorState *ed);

/* Loads `path` into the editor, remembering it for future saves regardless of success
   (so Ctrl+S can create a brand new file). Returns false if the file could not be read. */
bool editor_load(EditorState *ed, const char *path);
bool editor_save(EditorState *ed);

/* Basename of ed->filePath, or "Untitled" if the editor has no associated file yet. */
const char *editor_display_name(const EditorState *ed);

void editor_insert_utf8(EditorState *ed, const char *bytes, int n);
void editor_paste(EditorState *ed, const char *text, int len);
void editor_backspace(EditorState *ed);
void editor_delete_forward(EditorState *ed);
void editor_enter(EditorState *ed);

bool editor_undo(EditorState *ed);
bool editor_redo(EditorState *ed);

/* Flips a task-list block between checked/unchecked (undo-wrapped). Returns false if
   `blockIndex` is out of range or isn't a task block. */
bool editor_toggle_task(EditorState *ed, int blockIndex);

/* Converts ed->cursorBlock to `newType`, undo-wrapped. quick_insert clears the block's text
   first (it's only ever called on a bare "@word" typed for exactly this purpose); turn_into
   preserves whatever the block already held. Both scrub embedded '\n' bytes and clear `lang`
   when leaving BLOCK_CODE, and both handle BLOCK_HR's "insert a following empty paragraph and
   move the cursor there" special case (quick_insert only -- turn_into never offers BLOCK_HR). */
void editor_quick_insert_apply(EditorState *ed, BlockType newType);
void editor_turn_into_apply(EditorState *ed, BlockType newType);

/* Inserts a rows*cols GFM table (row 0 as BLOCK_TABLE_HEADER_CELL, the rest BLOCK_TABLE_CELL,
   empty text, left-aligned), undo-wrapped. Only ever called on the bare "@word" the user just
   typed (like editor_quick_insert_apply) -- always replaces ed->cursorBlock, discarding
   whatever it held. Cursor lands in the first header cell. */
void editor_insert_table(EditorState *ed, int rows, int cols);

/* Tab (dir=+1) / Shift+Tab (dir=-1) cell navigation: table cells are always laid out as one
   contiguous, row-major run of blocks, so moving to the next/previous cell is just cursorBlock
   +/- 1 (no row/col arithmetic needed) -- wrapping row-to-row happens for free. At either end of
   the table, moves into the following/preceding block if one exists, or inserts a fresh empty
   paragraph after the table if it's document-final. No-op if ed->cursorBlock isn't a table cell,
   or (Shift+Tab from the table's very first cell) nowhere to go. Not undo-wrapped itself -- the
   occasional "insert a trailing paragraph" side effect is treated as harmless bookkeeping, same
   as how plain cursor movement never touches undo; callers that need it wrapped (none today) can
   wrap it themselves. */
void editor_table_move_cell(EditorState *ed, int dir);

/* `extend`: false moves the caret and collapses/resets the selection anchor to it (plain
   movement); true moves only the caret, extending the selection from the existing anchor
   (Shift+movement). */
void editor_move_left(EditorState *ed, bool extend);
void editor_move_right(EditorState *ed, bool extend);
void editor_move_home(EditorState *ed, bool extend);
void editor_move_end(EditorState *ed, bool extend);
void editor_move_vertical(EditorState *ed, LayoutCache *cache, SDL2_Font *fonts, int dir, bool extend);

/* Mouse press: moves the caret and (re)establishes the selection anchor there. */
void editor_click(EditorState *ed, LayoutCache *cache, SDL2_Font *fonts, Clay_Vector2 point);
/* Mouse drag (button held after the initial press): moves only the caret, extending the
   selection from whatever editor_click most recently set as the anchor. */
void editor_drag_to(EditorState *ed, LayoutCache *cache, SDL2_Font *fonts, Clay_Vector2 point);

bool editor_has_selection(const EditorState *ed);
/* Normalizes the anchor/cursor pair into forward document order. */
void editor_selection_range(const EditorState *ed, int *startBlock, int *startOffset, int *endBlock, int *endOffset);
/* Returns the selected text (blocks joined with '\n'), or NULL if there is no selection.
   Caller must free() the result. */
char *editor_selection_to_text(const EditorState *ed, int *outLen);

#endif
