/**
 * Fresh Editor TypeScript Plugin API
 *
 * This file provides type definitions for the Fresh editor's TypeScript plugin system.
 * Plugins have access to the global `editor` object which provides methods to:
 * - Query editor state (buffers, cursors, viewports)
 * - Modify buffer content (insert, delete text)
 * - Add visual decorations (overlays, highlighting)
 * - Interact with the editor UI (status messages, prompts)
 *
 * Note: types/fresh.d.ts is auto-generated from this template and src/ts_runtime.rs
 *
 * ## Core Concepts
 *
 * ### Buffers
 * A buffer holds text content and may or may not be associated with a file.
 * Each buffer has a unique numeric ID that persists for the editor session.
 * Buffers track their content, modification state, cursor positions, and path.
 * All text operations (insert, delete, read) use byte offsets, not character indices.
 *
 * ### Splits
 * A split is a viewport pane that displays a buffer. The editor can have multiple
 * splits arranged in a tree layout. Each split shows exactly one buffer, but the
 * same buffer can be displayed in multiple splits. Use split IDs to control which
 * pane displays which buffer.
 *
 * ### Virtual Buffers
 * Special buffers created by plugins to display structured data like search results,
 * diagnostics, or git logs. Virtual buffers support text properties (metadata attached
 * to text ranges) that plugins can query when the user selects a line. Unlike normal
 * buffers, virtual buffers are typically read-only and not backed by files.
 *
 * ### Text Properties
 * Metadata attached to text ranges in virtual buffers. Each entry has text content
 * and a properties object with arbitrary key-value pairs. Use `getTextPropertiesAtCursor`
 * to retrieve properties at the cursor position (e.g., to get file/line info for "go to").
 *
 * ### Overlays
 * Visual decorations applied to buffer text without modifying content. Overlays can
 * change text color and add underlines. Use overlay IDs to manage them; prefix IDs
 * enable batch removal (e.g., "lint:" prefix for all linter highlights).
 *
 * ### Modes
 * Keybinding contexts that determine how keypresses are interpreted. Each buffer has
 * a mode (e.g., "normal", "insert", "special"). Custom modes can inherit from parents
 * and define buffer-local keybindings. Virtual buffers typically use custom modes.
 */

/**
 * Get the editor API instance.
 * Plugins must call this at the top of their file to get a scoped editor object.
 * @returns The editor API object for this plugin
 * @example
 * const editor = getEditor();
 */
declare function getEditor(): EditorAPI;

/**
 * Plugin-specific methods added by the JavaScript runtime wrapper.
 * These extend the base EditorAPI with i18n and command registration helpers.
 */
interface EditorAPI {
  /**
   * Translate a string using the plugin's i18n file
   * @param key - Translation key (e.g., "status.ready")
   * @param args - Optional interpolation arguments
   * @returns Translated string
   */
  t(key: string, args?: Record<string, string>): string;

  /**
   * Get the i18n helper object (for compatibility)
   * @returns Object with t() method bound to this plugin
   */
  getL10n(): { t: (key: string, args?: Record<string, string>) => string };

  /**
   * Register a custom command (plugin wrapper - source is added automatically)
   * @param name - Command name (use %key for i18n)
   * @param description - Command description (use %key for i18n)
   * @param action - Global function name to call
   * @param contexts - Comma-separated contexts (default: "")
   * @returns true if command was registered
   */
  registerCommand(name: string, description: string, action: string, contexts?: string): boolean;

  /**
   * Copy text to system clipboard (alias for setClipboard)
   * @param text - Text to copy
   */
  copyToClipboard(text: string): void;

  /**
   * Join path segments into a single path (variadic version)
   * @param parts - Path segments to join
   * @returns Joined path string
   */
  pathJoin(...parts: string[]): string;

  /**
   * Add a visual overlay to buffer text (with optional parameters)
   * Most parameters have defaults: bold=false, italic=false, bg=-1 (transparent), extend=false
   */
  addOverlay(
    buffer_id: number,
    namespace: string,
    start: number,
    end: number,
    r: number,
    g: number,
    b: number,
    underline: boolean,
    bold?: boolean,
    italic?: boolean,
    bg_r?: number,
    bg_g?: number,
    bg_b?: number,
    extend_to_line_end?: boolean
  ): boolean;

  /**
   * Get the theme JSON Schema (with proper typing)
   */
  getThemeSchema(): {
    $defs?: Record<string, Record<string, unknown>>;
    properties?: Record<string, unknown>;
  };

  /**
   * Get built-in themes as a map of name to JSON string
   */
  getBuiltinThemes(): Record<string, string>;
}

/**
 * Buffer identifier (unique numeric ID)
 */
type BufferId = number;

/** View token wire format for view transforms */
interface ViewTokenWire {
  /** Source byte offset (null for injected view-only content) */
  source_offset: number | null;
  /** Token kind: Text, Newline, Space, or Break */
  kind: ViewTokenWireKind;
}

/** View token kind discriminated union */
type ViewTokenWireKind =
  | { Text: string }
  | "Newline"
  | "Space"
  | "Break";

/** Layout hints for compose mode */
interface LayoutHints {
  /** Optional compose width for centering/wrapping */
  compose_width?: number | null;
  /** Optional column guides for tables */
  column_guides?: number[] | null;
}

/** Handle for a cancellable process spawned with spawnProcess */
interface ProcessHandle extends PromiseLike<SpawnResult> {
  /** Promise that resolves to the process ID */
  readonly processId: Promise<number>;
  /** Promise that resolves to the result when the process completes */
  readonly result: Promise<SpawnResult>;
  /** Kill the process. Returns true if killed, false if already completed */
  kill(): Promise<boolean>;
}

/** File explorer decoration entry provided by plugins */
interface FileExplorerDecoration {
  /** Absolute or workspace-relative path to decorate */
  path: string;
  /** Symbol to display (single character recommended) */
  symbol?: string | null;
  /** RGB color for the symbol */
  color?: [u8; 3] | null;
  /** Priority for resolving conflicts (higher wins) */
  priority?: number | null;
}

/** Result from spawnProcess */
interface SpawnResult {
  /** Complete stdout as string. Newlines preserved; trailing newline included. */
  stdout: string;
  /** Complete stderr as string. Contains error messages and warnings. */
  stderr: string;
  /** Process exit code. 0 usually means success; -1 if process was killed. */
  exit_code: number;
}

/** Result from spawnBackgroundProcess - just the process ID */
interface BackgroundProcessResult {
  /** Unique process ID for later reference (kill, status check) */
  process_id: number;
}

/** File stat information */
interface FileStat {
  /** Whether the path exists */
  exists: boolean;
  /** Whether the path is a file */
  is_file: boolean;
  /** Whether the path is a directory */
  is_dir: boolean;
  /** File size in bytes */
  size: number;
  /** Whether the file is read-only */
  readonly: boolean;
}

/** Buffer information */
interface BufferInfo {
  /** Unique buffer ID */
  id: number;
  /** File path (empty string if no path) */
  path: string;
  /** Whether buffer has unsaved changes */
  modified: boolean;
  /** Buffer length in bytes */
  length: number;
}

/** Diff vs last save for a buffer */
interface TsBufferSavedDiff {
  equal: boolean;
  byte_ranges: [number, number][];
  line_ranges?: [number, number][] | null;
}

/** Selection range */
interface SelectionRange {
  /** Start byte position */
  start: number;
  /** End byte position */
  end: number;
}

/** Cursor information with optional selection */
interface CursorInfo {
  /** Byte position of the cursor */
  position: number;
  /** Selection range if text is selected, null otherwise */
  selection?: SelectionRange | null;
}

/** LSP diagnostic position */
interface TsDiagnosticPosition {
  line: number;
  character: number;
}

/** LSP diagnostic range */
interface TsDiagnosticRange {
  start: TsDiagnosticPosition;
  end: TsDiagnosticPosition;
}

/** LSP diagnostic item for TypeScript plugins */
interface TsDiagnostic {
  /** File URI (e.g., "file:///path/to/file.rs") */
  uri: string;
  /** Diagnostic severity: 1=Error, 2=Warning, 3=Info, 4=Hint */
  severity: number;
  /** Diagnostic message */
  message: string;
  /** Source of the diagnostic (e.g., "rust-analyzer") */
  source?: string | null;
  /** Location range in the file */
  range: TsDiagnosticRange;
}

/** Viewport information */
interface ViewportInfo {
  /** Byte offset of the top-left visible position */
  top_byte: number;
  /** Column offset for horizontal scrolling */
  left_column: number;
  /** Viewport width in columns */
  width: number;
  /** Viewport height in rows */
  height: number;
}

/** Suggestion for prompt autocomplete */
interface PromptSuggestion {
  /** Display text for the suggestion */
  text: string;
  /** Optional description shown alongside */
  description?: string | null;
  /** Optional value to use instead of text when selected */
  value?: string | null;
  /** Whether the suggestion is disabled */
  disabled?: boolean | null;
  /** Optional keybinding hint */
  keybinding?: string | null;
}

/** Directory entry from readDir */
interface DirEntry {
  /** Entry name only (not full path). Join with parent path to get absolute path. */
  name: string;
  /** True if entry is a regular file */
  is_file: boolean;
  /** True if entry is a directory. Note: symlinks report the target type. */
  is_dir: boolean;
}

/** Entry for virtual buffer content with embedded metadata */
interface TextPropertyEntry {
  /** Text to display. Include trailing newline for separate lines. */
  text: string;
  /** Arbitrary metadata queryable via getTextPropertiesAtCursor. */
  properties: Record<string, unknown>;
}

/** Result from createVirtualBufferInSplit */
interface CreateVirtualBufferResult {
  buffer_id: number;
  split_id?: number | null;
}

/** Configuration for createVirtualBufferInSplit */
interface CreateVirtualBufferOptions {
  /** Buffer name shown in status bar (convention: "*Name*") */
  name: string;
  /** Mode for keybindings; define with defineMode first */
  mode: string;
  /** Prevent text modifications */
  read_only: boolean;
  /** Content with embedded metadata */
  entries: TextPropertyEntry[];
  /** Split ratio (0.3 = new pane gets 30% of space) */
  ratio: number;
  /** Split direction: "horizontal" (below) or "vertical" (side-by-side). Default: horizontal */
  direction?: string | null;
  /** If set and panel exists, update content instead of creating new buffer */
  panel_id?: string | null;
  /** Show line numbers gutter (default: true) */
  show_line_numbers?: boolean | null;
  /** Show cursor in buffer (default: true) */
  show_cursors?: boolean | null;
  /** Disable all editing commands (default: false) */
  editing_disabled?: boolean | null;
  /** Enable/disable line wrapping (None = use global setting) */
  line_wrap?: boolean | null;
}

/** Options for creating a virtual buffer in an existing split */
interface CreateVirtualBufferInExistingSplitOptions {
  /** Display name (e.g., "*Commit Details*") */
  name: string;
  /** Mode name for buffer-local keybindings */
  mode: string;
  /** Whether the buffer is read-only */
  read_only: boolean;
  /** Entries with text and embedded properties */
  entries: TextPropertyEntry[];
  /** Target split ID where the buffer should be displayed */
  split_id: number;
  /** Whether to show line numbers in the buffer (default true) */
  show_line_numbers?: boolean | null;
  /** Whether to show cursors in the buffer (default true) */
  show_cursors?: boolean | null;
  /** Whether editing is disabled for this buffer (default false) */
  editing_disabled?: boolean | null;
  /** Enable/disable line wrapping (None = use global setting) */
  line_wrap?: boolean | null;
}

/** Options for creating a virtual buffer in the current split as a new tab */
interface CreateVirtualBufferInCurrentSplitOptions {
  /** Display name (e.g., "*Help*") */
  name: string;
  /** Mode name for buffer-local keybindings */
  mode: string;
  /** Whether the buffer is read-only */
  read_only: boolean;
  /** Entries with text and embedded properties */
  entries: TextPropertyEntry[];
  /** Whether to show line numbers in the buffer (default false for help/docs) */
  show_line_numbers?: boolean | null;
  /** Whether to show cursors in the buffer (default true) */
  show_cursors?: boolean | null;
  /** Whether editing is disabled for this buffer (default false) */
  editing_disabled?: boolean | null;
  /** Whether this buffer should be hidden from tabs (for composite source buffers) */
  hidden_from_tabs?: boolean | null;
}

/** Layout configuration for composite buffers */
interface TsCompositeLayoutConfig {
  /** Layout type: "side-by-side", "stacked", or "unified" */
  layout_type: string;
  /** Relative widths for side-by-side layout (e.g., [0.5, 0.5]) */
  ratios?: number[] | null;
  /** Show separator between panes */
  show_separator?: boolean | null;
  /** Spacing between stacked panes */
  spacing?: number | null;
}

/** Pane style configuration */
interface TsCompositePaneStyle {
  /** Background color for added lines (RGB tuple) */
  add_bg?: [number, number, number] | null;
  /** Background color for removed lines (RGB tuple) */
  remove_bg?: [number, number, number] | null;
  /** Background color for modified lines (RGB tuple) */
  modify_bg?: [number, number, number] | null;
  /** Gutter style: "line-numbers", "diff-markers", "both", "none" */
  gutter_style?: string | null;
}

/** Source pane configuration for composite buffers */
interface TsCompositeSourceConfig {
  /** Buffer ID to display in this pane */
  buffer_id: number;
  /** Label for the pane (shown in header) */
  label?: string | null;
  /** Whether the pane is editable */
  editable: boolean;
  /** Pane styling options */
  style?: TsCompositePaneStyle | null;
}

/** Diff hunk configuration */
interface TsCompositeHunk {
  /** Start line in old file (0-indexed) */
  old_start: number;
  /** Number of lines in old file */
  old_count: number;
  /** Start line in new file (0-indexed) */
  new_start: number;
  /** Number of lines in new file */
  new_count: number;
}

/** Options for creating a composite buffer */
interface CreateCompositeBufferOptions {
  /** Display name for the composite buffer (shown in tab) */
  name: string;
  /** Mode for keybindings (e.g., "diff-view") */
  mode: string;
  /** Layout configuration */
  layout: TsCompositeLayoutConfig;
  /** Source panes to display */
  sources: TsCompositeSourceConfig[];
  /** Optional diff hunks for line alignment */
  hunks?: TsCompositeHunk[] | null;
}

/** JavaScript representation of ActionSpec (with optional count) */
interface ActionSpecJs {
  action: string;
  count?: number | null;
}

/** TypeScript struct for action popup action */
interface TsActionPopupAction {
  id: string;
  label: string;
}

/** TypeScript struct for action popup options */
interface TsActionPopupOptions {
  id: string;
  title: string;
  message: string;
  actions: TsActionPopupAction[];
}

/**
 * Main editor API interface
 */
interface EditorAPI {
  // === Status and Logging ===
  /**
   * Display a transient message in the editor's status bar
   *
   * The message will be shown until the next status update or user action.
   * Use for feedback on completed operations (e.g., "File saved", "2 matches found").
   * @param message - Text to display; keep short (status bar has limited width)
   */
  setStatus(message: string): void;
  /**
   * Log a debug message from a plugin
   *
   * Messages appear in log file when running with RUST_LOG=debug.
   * Useful for plugin development and troubleshooting.
   * @param message - Debug message; include context like function name and relevant values
   */
  debug(message: string): void;

  // === Buffer Queries ===
  /**
   * Get the theme JSON Schema for the theme editor
   *
   * Returns the raw JSON Schema generated by schemars for ThemeFile.
   * The schema uses standard JSON Schema format with $ref for type references.
   * Plugins are responsible for parsing the schema and resolving $ref references.
   * @returns JSON Schema object
   */
  getThemeSchema(): unknown;
  getBuiltinThemes(): unknown;
  /**
   * Get the current editor configuration
   *
   * Returns the merged configuration (user config file + compiled-in defaults).
   * This is the runtime config that the editor is actually using, including
   * all default values for LSP servers, languages, keybindings, etc.
   * @returns Configuration object
   */
  getConfig(): unknown;
  /**
   * Get the user's configuration (only explicitly set values)
   *
   * Returns only the configuration from the user's config file.
   * Fields not present here are using default values.
   * Use this with getConfig() to determine which values are defaults.
   * @returns User configuration object (sparse - only explicitly set values)
   */
  getUserConfig(): unknown;
  /**
   * Get the user configuration directory path
   *
   * Returns the absolute path to the directory where user config and themes are stored.
   * e.g. ~/.config/fresh/ on Linux or ~/Library/Application Support/fresh/ on macOS.
   */
  getConfigDir(): string;
  /**
   * Get the user themes directory path
   *
   * Returns the absolute path to the directory where user themes are stored.
   * e.g. ~/.config/fresh/themes/
   */
  getThemesDir(): string;
  /**
   * Get the buffer ID of the focused editor pane
   *
   * Returns 0 if no buffer is active (rare edge case).
   * Use this ID with other buffer operations like insertText.
   */
  getActiveBufferId(): number;
  /**
   * Get the byte offset of the primary cursor in the active buffer
   *
   * Returns 0 if no cursor exists. For multi-cursor scenarios, use getAllCursors
   * to get all cursor positions with selection info.
   * Note: This is a byte offset, not a character index (UTF-8 matters).
   */
  getCursorPosition(): number;
  /**
   * Get the absolute file path for a buffer
   *
   * Returns empty string for unsaved buffers or virtual buffers.
   * The path is always absolute. Use this to determine file type,
   * construct related paths, or display to the user.
   * @param buffer_id - Target buffer ID
   */
  getBufferPath(buffer_id: number): string;
  /**
   * Get the total byte length of a buffer's content
   *
   * Returns 0 if buffer doesn't exist.
   * @param buffer_id - Target buffer ID
   */
  getBufferLength(buffer_id: number): number;
  /**
   * Check if a buffer has been modified since last save
   *
   * Returns false if buffer doesn't exist or has never been saved.
   * Virtual buffers are never considered modified.
   * @param buffer_id - Target buffer ID
   */
  isBufferModified(buffer_id: number): boolean;
  /** Get the currently active locale */
  getCurrentLocale(): string;
  /**
   * Get the ID of the focused split pane
   *
   * Use with focusSplit, setSplitBuffer, or createVirtualBufferInExistingSplit
   * to manage split layouts.
   */
  getActiveSplitId(): number;
  /**
   * Get the line number of the primary cursor (1-indexed)
   *
   * Line numbers start at 1. Returns 1 if no cursor exists.
   * For byte offset use getCursorPosition instead.
   */
  getCursorLine(): number;
  /**
   * Get byte offsets of all cursors (multi-cursor support)
   *
   * Returns array of positions; empty if no cursors. Primary cursor
   * is typically first. For selection info use getAllCursors instead.
   */
  getAllCursorPositions(): number[];
  /**
   * Check if a background process is still running
   *
   * @param process_id - ID returned from spawnBackgroundProcess
   * @returns true if process is running, false if not found or exited
   */
  isProcessRunning(process_id: number): boolean;
  /** Compute syntax highlighting for a buffer range */
  getHighlights(buffer_id: number, start: number, end: number): Promise<TsHighlightSpan[]>;
  /** Get diff vs last saved snapshot for a buffer */
  getBufferSavedDiff(buffer_id: number): TsBufferSavedDiff | null;
  /**
   * Get all LSP diagnostics across all files
   * @returns Array of Diagnostic objects with file URI, severity, message, and range
   */
  getAllDiagnostics(): TsDiagnostic[];
  /**
   * Get text from a buffer range
   *
   * Used by vi mode plugin for yank operations - reads text without deleting.
   * @param buffer_id - Buffer ID
   * @param start - Start byte offset
   * @param end - End byte offset
   * @returns Text content of the range, or empty string on error
   */
  getBufferText(buffer_id: number, start: number, end: number): Promise<string>;
  /**
   * Get the current global editor mode
   *
   * @returns Current mode name or null if no mode is active
   */
  getEditorMode(): string;

  // === Buffer Info Queries ===
  /**
   * Get full information about a buffer
   * @param buffer_id - Buffer ID
   * @returns BufferInfo object or null if buffer not found
   */
  getBufferInfo(buffer_id: number): BufferInfo | null;
  /**
   * List all open buffers
   * @returns Array of BufferInfo objects
   */
  listBuffers(): BufferInfo[];
  /**
   * Get primary cursor with selection info
   * @returns CursorInfo object or null if no cursor
   */
  getPrimaryCursor(): CursorInfo | null;
  /**
   * Get all cursors (for multi-cursor support)
   * @returns Array of CursorInfo objects
   */
  getAllCursors(): CursorInfo[];
  /**
   * Get viewport information
   * @returns ViewportInfo object or null if no viewport
   */
  getViewport(): ViewportInfo | null;

  // === Prompt Operations ===
  /**
   * Start an interactive prompt
   * @param label - Label to display (e.g., "Git grep: ")
   * @param prompt_type - Type identifier (e.g., "git-grep")
   * @returns true if prompt was started successfully
   */
  startPrompt(label: string, prompt_type: string): boolean;
  /**
   * Set suggestions for the current prompt
   * @param suggestions - Array of suggestions to display
   * @returns true if suggestions were set successfully
   */
  setPromptSuggestions(suggestions: PromptSuggestion[]): boolean;

  // === Buffer Mutations ===
  /**
   * Apply a theme by name
   *
   * Loads and applies the specified theme immediately. The theme can be a built-in
   * theme name or a custom theme from the themes directory.
   * @param theme_name - Name of the theme to apply (e.g., "dark", "light", "my-custom-theme")
   */
  applyTheme(theme_name: string): void;
  /**
   * Reload configuration from file
   *
   * After a plugin saves config changes to the config file, call this to reload
   * the editor's in-memory configuration. This ensures the editor and plugins
   * stay in sync with the saved config.
   */
  reloadConfig(): void;
  /**
   * Log an error message from a plugin
   *
   * Messages appear in log file when running with RUST_LOG=error.
   * Use for critical errors that need attention.
   * @param message - Error message
   */
  error(message: string): void;
  /**
   * Log a warning message from a plugin
   *
   * Messages appear in log file when running with RUST_LOG=warn.
   * Use for warnings that don't prevent operation but indicate issues.
   * @param message - Warning message
   */
  warn(message: string): void;
  /**
   * Log an info message from a plugin
   *
   * Messages appear in log file when running with RUST_LOG=info.
   * Use for important operational messages.
   * @param message - Info message
   */
  info(message: string): void;
  /**
   * Copy text to the system clipboard
   *
   * Copies the provided text to both the internal and system clipboard.
   * Uses OSC 52 and arboard for cross-platform compatibility.
   * @param text - Text to copy to clipboard
   */
  setClipboard(text: string): void;
  /**
   * Insert text at a byte position in a buffer
   *
   * Text is inserted before the byte at position. Position must be valid
   * (0 to buffer length). Insertion shifts all text after position.
   * Operation is asynchronous; returns true if command was sent successfully.
   * @param buffer_id - Target buffer ID
   * @param position - Byte offset where text will be inserted (must be at char boundary)
   * @param text - UTF-8 text to insert
   */
  insertText(buffer_id: number, position: number, text: string): boolean;
  /**
   * Delete a byte range from a buffer
   *
   * Deletes bytes from start (inclusive) to end (exclusive).
   * Both positions must be at valid UTF-8 char boundaries.
   * Operation is asynchronous; returns true if command was sent successfully.
   * @param buffer_id - Target buffer ID
   * @param start - Start byte offset (inclusive)
   * @param end - End byte offset (exclusive)
   */
  deleteRange(buffer_id: number, start: number, end: number): boolean;
  /**
   * Clear all overlays in a namespace
   * @param buffer_id - The buffer ID
   * @param namespace - The namespace to clear
   * @returns true if successful
   */
  clearNamespace(buffer_id: number, namespace: string): boolean;
  /**
   * Enable/disable line numbers for a buffer
   * @param buffer_id - The buffer ID
   * @param enabled - Whether to show line numbers
   * @returns true if successful
   */
  setLineNumbers(buffer_id: number, enabled: boolean): boolean;
  /**
   * Add a virtual line above or below a source line
   * @param buffer_id - The buffer ID
   * @param position - Byte position to anchor the virtual line to
   * @param text - The text content of the virtual line
   * @param fg_r - Foreground red color component (0-255)
   * @param fg_g - Foreground green color component (0-255)
   * @param fg_b - Foreground blue color component (0-255)
   * @param bg_r - Background red color component (0-255), -1 for transparent
   * @param bg_g - Background green color component (0-255), -1 for transparent
   * @param bg_b - Background blue color component (0-255), -1 for transparent
   * @param above - Whether to insert above (true) or below (false) the line
   * @param namespace - Namespace for bulk removal (e.g., "git-blame")
   * @param priority - Priority for ordering multiple lines at same position
   * @returns true if virtual line was added
   */
  addVirtualLine(buffer_id: number, position: number, text: string, fg_r: number, fg_g: number, fg_b: number, bg_r: number, bg_g: number, bg_b: number, above: boolean, namespace: string, priority: number): boolean;
  /**
   * Set a line indicator in the gutter's indicator column
   * @param buffer_id - The buffer ID
   * @param line - Line number (0-indexed)
   * @param namespace - Namespace for grouping (e.g., "git-gutter", "breakpoints")
   * @param symbol - Symbol to display (e.g., "│", "●", "★")
   * @param r - Red color component (0-255)
   * @param g - Green color component (0-255)
   * @param b - Blue color component (0-255)
   * @param priority - Priority for display when multiple indicators exist (higher wins)
   * @returns true if indicator was set
   */
  setLineIndicator(buffer_id: number, line: number, namespace: string, symbol: string, r: number, g: number, b: number, priority: number): boolean;
  /**
   * Clear all line indicators for a specific namespace
   * @param buffer_id - The buffer ID
   * @param namespace - Namespace to clear (e.g., "git-gutter")
   * @returns true if indicators were cleared
   */
  clearLineIndicators(buffer_id: number, namespace: string): boolean;
  /**
   * Set file explorer decorations for a namespace
   * @param namespace - Namespace for grouping (e.g., "git-status")
   * @param decorations - Decoration entries
   * @returns true if decorations were accepted
   */
  setFileExplorerDecorations(namespace: string, decorations: FileExplorerDecoration[]): boolean;
  /**
   * Clear file explorer decorations for a namespace
   * @param namespace - Namespace to clear (e.g., "git-status")
   * @returns true if decorations were cleared
   */
  clearFileExplorerDecorations(namespace: string): boolean;
  /**
   * Submit a transformed view stream for a viewport
   * @param buffer_id - Buffer to apply the transform to
   * @param start - Viewport start byte
   * @param end - Viewport end byte
   * @param tokens - Array of tokens with source offsets
   * @param source_map - Array of source offsets (null for injected)
   * @param layout_hints - Optional layout hints (compose width, column guides)
   */
  submitViewTransform(buffer_id: number, split_id?: number | null, start: number, end: number, tokens: ViewTokenWire[], layout_hints?: LayoutHints | null): boolean;
  /**
   * Clear view transform for a buffer/split (returns to normal rendering)
   * @param buffer_id - Buffer ID
   * @param split_id - Optional split ID (uses active split if not specified)
   * @returns true if clear succeeded
   */
  clearViewTransform(buffer_id: number, split_id?: number | null): boolean;
  /**
   * Insert text at the current cursor position in the active buffer
   * @param text - The text to insert
   * @returns true if insertion succeeded
   */
  insertAtCursor(text: string): boolean;
  /** Translate a string for a plugin using the current locale */
  pluginTranslate(plugin_name: string, key: string, args: Record<string, unknown>): string;
  /** Register a custom command that can be triggered by keybindings or the command palette */
  registerCommand(name: string, description: string, action: string, contexts: string, source: string): boolean;
  /**
   * Unregister a custom command by name
   * @param name - The name of the command to unregister
   * @returns true if the command was successfully unregistered
   */
  unregisterCommand(name: string): boolean;
  /**
   * Set or unset a custom context for command visibility
   * Custom contexts allow plugins to control when their commands are available.
   * For example, setting "config-editor" context makes config editor commands visible.
   * @param name - Context name (e.g., "config-editor")
   * @param active - Whether the context is active (true = set, false = unset)
   * @returns true if the context was updated
   */
  setContext(name: string, active: boolean): boolean;
  /**
   * Open a file in the editor, optionally at a specific location
   * @param path - File path to open
   * @param line - Line number to jump to (0 for no jump)
   * @param column - Column number to jump to (0 for no jump)
   * @returns true if file was opened
   */
  openFile(path: string, line: number, column: number): boolean;
  /**
   * Open a file in a specific split pane
   * @param split_id - The split ID to open the file in
   * @param path - File path to open
   * @param line - Line number to jump to (0 for no jump)
   * @param column - Column number to jump to (0 for no jump)
   * @returns true if file was opened
   */
  openFileInSplit(split_id: number, path: string, line: number, column: number): boolean;
  /**
   * Spawn a long-running background process
   *
   * Unlike spawnProcess which waits for completion, this starts a process
   * in the background and returns immediately with a process ID.
   * Use killProcess(id) to terminate the process later.
   * Use isProcessRunning(id) to check if it's still running.
   *
   * @param command - Program name (searched in PATH) or absolute path
   * @param args - Command arguments (each array element is one argument)
   * @param cwd - Working directory; null uses editor's cwd
   * @returns Object with process_id for later reference
   * @example
   * const proc = await editor.spawnBackgroundProcess("asciinema", ["rec", "output.cast"]);
   * // Later...
   * await editor.killProcess(proc.process_id);
   */
  spawnBackgroundProcess(command: string, args: string[], cwd?: string | null): Promise<BackgroundProcessResult>;
  /**
   * Kill a background or cancellable process by ID
   *
   * Sends SIGTERM to gracefully terminate the process.
   * Returns true if the process was found and killed, false if not found.
   *
   * @param process_id - ID returned from spawnBackgroundProcess or spawnProcessStart
   * @returns true if process was killed, false if not found
   */
  killProcess(process_id: number): Promise<boolean>;
  /**
   * Wait for a cancellable process to complete and get its result
   *
   * @param process_id - ID returned from spawnProcessStart
   * @returns SpawnResult with stdout, stderr, and exit_code
   */
  spawnProcessWait(process_id: number): Promise<SpawnResult>;
  /**
   * Delay execution for a specified number of milliseconds
   *
   * Useful for debouncing user input or adding delays between operations.
   * @param ms - Number of milliseconds to delay
   * @example
   * await editor.delay(100);  // Wait 100ms
   */
  delay(ms: number): Promise<void>;
  /** Find a buffer ID by its file path */
  findBufferByPath(path: string): number;
  /**
   * Start a prompt with pre-filled initial value
   * @param label - Label to display (e.g., "Git grep: ")
   * @param prompt_type - Type identifier (e.g., "git-grep")
   * @param initial_value - Initial text to pre-fill in the prompt
   * @returns true if prompt was started successfully
   */
  startPromptWithInitial(label: string, prompt_type: string, initial_value: string): boolean;
  /**
   * Delete a theme file by name
   *
   * Only deletes files from the user's themes directory.
   * This is a safe operation that prevents plugins from deleting arbitrary files.
   * @param name - Theme name (without .json extension)
   */
  deleteTheme(name: string): Promise<void>;
  /**
   * Create a composite buffer that displays multiple source buffers
   *
   * Composite buffers allow displaying multiple underlying buffers in a single
   * tab/view area with custom layouts (side-by-side, stacked, unified).
   * This is useful for diff views, merge conflict resolution, etc.
   * @param options - Configuration for the composite buffer
   * @returns Promise resolving to the buffer ID of the created composite buffer
   */
  createCompositeBuffer(options: CreateCompositeBufferOptions): Promise<number>;
  /**
   * Update line alignment for a composite buffer
   * @param buffer_id - The composite buffer ID
   * @param hunks - New diff hunks for alignment
   */
  updateCompositeAlignment(buffer_id: number, hunks: TsCompositeHunk[]): boolean;
  /**
   * Close a composite buffer
   * @param buffer_id - The composite buffer ID to close
   */
  closeCompositeBuffer(buffer_id: number): boolean;
  /**
   * Send an arbitrary LSP request and receive the raw JSON response
   * @param language - Language ID (e.g., "cpp")
   * @param method - Full LSP method (e.g., "textDocument/switchSourceHeader")
   * @param params - Optional request payload
   * @returns Promise resolving to the JSON response value
   */
  sendLspRequest(language: string, method: string, params?: unknown | null): Promise<unknown>;
  /**
   * Set the scroll position of a specific split
   * @param split_id - The split ID
   * @param top_byte - The byte offset of the top visible line
   * @returns true if successful
   */
  setSplitScroll(split_id: number, top_byte: number): boolean;
  /**
   * Set the ratio of a split container
   * @param split_id - ID of the split
   * @param ratio - Ratio between 0.0 and 1.0 (0.5 = equal split)
   * @returns true if the ratio was set successfully
   */
  setSplitRatio(split_id: number, ratio: number): boolean;
  /**
   * Distribute all visible splits evenly
   * This adjusts the ratios of all container splits so each leaf split gets equal space
   * @returns true if the command was sent successfully
   */
  distributeSplitsEvenly(): boolean;
  /**
   * Set cursor position in a buffer (also scrolls viewport to show cursor)
   * @param buffer_id - ID of the buffer
   * @param position - Byte offset position for the cursor
   * @returns true if the command was sent successfully
   */
  setBufferCursor(buffer_id: number, position: number): boolean;
  /**
   * Execute a built-in editor action by name
   *
   * This is used by vi mode plugin to run motions and then check cursor position.
   * For example, to implement "dw" (delete word), the plugin:
   * 1. Saves current cursor position
   * 2. Calls executeAction("move_word_right") - cursor moves
   * 3. Gets new cursor position
   * 4. Deletes from old to new position
   *
   * @param action_name - Action name (e.g., "move_word_right", "move_line_end")
   * @returns true if action was sent successfully
   */
  executeAction(action_name: string): boolean;
  /**
   * Execute multiple actions in sequence, each with an optional repeat count
   *
   * Used by vi mode for count prefix (e.g., "3dw" = delete 3 words).
   * All actions execute atomically with no plugin roundtrips between them.
   *
   * @param actions - Array of {action: string, count?: number} objects
   * @returns true if actions were sent successfully
   */
  executeActions(actions: ActionSpecJs[]): boolean;
  /**
   * Set the global editor mode (for modal editing like vi mode)
   *
   * When a mode is set, its keybindings take precedence over normal key handling.
   * Pass null/undefined to clear the mode and return to normal editing.
   *
   * @param mode - Mode name (e.g., "vi-normal") or null to clear
   * @returns true if command was sent successfully
   */
  setEditorMode(mode?: string | null): boolean;
  /**
   * Show an action popup with buttons for user interaction
   *
   * When the user selects an action, the ActionPopupResult hook is fired.
   * @param options - Popup configuration with id, title, message, and actions
   */
  showActionPopup(options: TsActionPopupOptions): boolean;
  /**
   * Disable LSP for a specific language and persist to config
   *
   * This is used by LSP helper plugins to let users disable LSP for languages
   * where the server is not available or not working.
   * @param language - The language to disable LSP for (e.g., "python", "rust")
   */
  disableLspForLanguage(language: string): boolean;
  /**
   * Create a scroll sync group for anchor-based synchronized scrolling
   *
   * Used for side-by-side diff views where two panes need to scroll together.
   * The plugin provides the group ID (must be unique per plugin).
   */
  createScrollSyncGroup(group_id: number, left_split: number, right_split: number): boolean;
  /**
   * Set sync anchors for a scroll sync group
   *
   * Anchors map corresponding line numbers between left and right buffers.
   * Each anchor is a tuple of (left_line, right_line).
   */
  setScrollSyncAnchors(group_id: number, anchors: [number, number][]): boolean;
  /** Remove a scroll sync group */
  removeScrollSyncGroup(group_id: number): boolean;

  /**
   * Spawn an external process and return a cancellable handle
   *
   * Returns a ProcessHandle that can be awaited for the result or killed early.
   * The handle is also a PromiseLike, so `await spawnProcess(...)` works directly.
   * @param command - Program name (searched in PATH) or absolute path
   * @param args - Command arguments (each array element is one argument)
   * @param cwd - Working directory; null uses editor's cwd
   * @example
   * // Simple usage (backward compatible)
   * const result = await editor.spawnProcess("git", ["status"]);
   *
   * // Cancellable usage
   * const search = editor.spawnProcess("rg", ["pattern"]);
   * // ... later, if user types new query:
   * search.kill();  // Cancel the search
   */
  spawnProcess(command: string, args?: string[], cwd?: string | null): ProcessHandle;
  // === Overlay Operations ===
  /**
   * Add a colored highlight overlay to text without modifying content
   *
   * Overlays are visual decorations that persist until explicitly removed.
   * Add an overlay (visual decoration) to a buffer
   * Use namespaces for easy batch removal (e.g., "spell", "todo").
   * Multiple overlays can apply to the same range; colors blend.
   * @param buffer_id - Target buffer ID
   * @param namespace - Optional namespace for grouping (use clearNamespace for batch removal)
   * @param start - Start byte offset
   * @param end - End byte offset
   * @param r - Red (0-255)
   * @param g - Green (0-255)
   * @param b - Blue (0-255)
   * @param underline - Add underline decoration
   * @param bold - Use bold text
   * @param italic - Use italic text
   * @param extend_to_line_end - Extend background to end of visual line
   * @returns true if overlay was added
   */
  addOverlay(buffer_id: number, namespace: string, start: number, end: number, r: number, g: number, b: number, bg_r: number, bg_g: number, bg_b: number, underline: boolean, bold: boolean, italic: boolean, extend_to_line_end: boolean): boolean;
  /**
   * Remove a specific overlay by its handle
   * @param buffer_id - The buffer ID
   * @param handle - The overlay handle to remove
   * @returns true if overlay was removed
   */
  removeOverlay(buffer_id: number, handle: string): boolean;
  /**
   * Clear all overlays that overlap with a byte range
   * @param buffer_id - The buffer ID
   * @param start - Start byte position (inclusive)
   * @param end - End byte position (exclusive)
   * @returns true if successful
   */
  clearOverlaysInRange(buffer_id: number, start: number, end: number): boolean;
  /**
   * Remove all overlays from a buffer
   * @param buffer_id - The buffer ID
   * @returns true if overlays were cleared
   */
  clearAllOverlays(buffer_id: number): boolean;
  /**
   * Add virtual text (inline decoration) at a position
   * @param buffer_id - The buffer ID
   * @param virtual_text_id - Unique identifier for this virtual text
   * @param position - Byte position to insert at
   * @param text - The virtual text to display
   * @param r - Red color component (0-255)
   * @param g - Green color component (0-255)
   * @param b - Blue color component (0-255)
   * @param before - Whether to insert before (true) or after (false) the position
   * @param use_bg - Whether to use the color as background (true) or foreground (false)
   * @returns true if virtual text was added
   */
  addVirtualText(buffer_id: number, virtual_text_id: string, position: number, text: string, r: number, g: number, b: number, before: boolean, use_bg: boolean): boolean;
  /**
   * Remove virtual text by ID
   * @param buffer_id - The buffer ID
   * @param virtual_text_id - The virtual text ID to remove
   * @returns true if virtual text was removed
   */
  removeVirtualText(buffer_id: number, virtual_text_id: string): boolean;
  /**
   * Remove all virtual texts with IDs starting with a prefix
   * @param buffer_id - The buffer ID
   * @param prefix - The prefix to match virtual text IDs against
   * @returns true if any virtual texts were removed
   */
  removeVirtualTextsByPrefix(buffer_id: number, prefix: string): boolean;
  /**
   * Remove all virtual texts from a buffer
   * @param buffer_id - The buffer ID
   * @returns true if virtual texts were cleared
   */
  clearVirtualTexts(buffer_id: number): boolean;
  /**
   * Clear all virtual texts in a namespace
   * @param buffer_id - The buffer ID
   * @param namespace - The namespace to clear (e.g., "git-blame")
   * @returns true if namespace was cleared
   */
  clearVirtualTextNamespace(buffer_id: number, namespace: string): boolean;
  /**
   * Force a refresh of line display for a buffer
   * @param buffer_id - The buffer ID
   * @returns true if refresh was triggered
   */
  refreshLines(buffer_id: number): boolean;

  // === File System Operations ===
  /**
   * Read entire file contents as UTF-8 string
   *
   * Throws if file doesn't exist, isn't readable, or isn't valid UTF-8.
   * For binary files, this will fail. For large files, consider memory usage.
   * @param path - File path (absolute or relative to cwd)
   */
  readFile(path: string): Promise<string>;
  /**
   * Write string content to a NEW file (fails if file exists)
   *
   * Creates a new file with the given content. Fails if the file already exists
   * to prevent plugins from accidentally overwriting user data.
   * @param path - Destination path (absolute or relative to cwd)
   * @param content - UTF-8 string to write
   */
  writeFile(path: string, content: string): Promise<void>;
  /**
   * Check if a path exists (file, directory, or symlink)
   *
   * Does not follow symlinks; returns true for broken symlinks.
   * Use fileStat for more detailed information.
   * @param path - Path to check (absolute or relative to cwd)
   */
  fileExists(path: string): boolean;
  /**
   * Get metadata about a file or directory
   *
   * Follows symlinks. Returns exists=false for non-existent paths
   * rather than throwing. Size is in bytes; directories may report 0.
   * @param path - Path to stat (absolute or relative to cwd)
   */
  fileStat(path: string): FileStat;
  /**
   * List directory contents
   *
   * Returns unsorted entries with type info. Entry names are relative
   * to the directory (use pathJoin to construct full paths).
   * Throws on permission errors or if path is not a directory.
   * @param path - Directory path (absolute or relative to cwd)
   * @example
   * const entries = editor.readDir("/home/user");
   * for (const e of entries) {
   * const fullPath = editor.pathJoin("/home/user", e.name);
   * }
   */
  readDir(path: string): DirEntry[];

  // === Environment Operations ===
  /**
   * Get an environment variable
   * @param name - Name of environment variable
   * @returns Value if set, null if not set
   */
  getEnv(name: string): string;
  /**
   * Get the editor's current working directory
   *
   * Returns the editor's working directory (set when the editor was started).
   * Use as base for resolving relative paths and spawning processes.
   * Note: This returns the editor's stored working_dir, not process CWD,
   * which is important for test isolation.
   */
  getCwd(): string;

  // === Path Operations ===
  /**
   * Join path segments using the OS path separator
   *
   * Handles empty segments and normalizes separators.
   * If a segment is absolute, previous segments are discarded.
   * @param parts - Path segments to join
   * @example
   * pathJoin("/home", "user", "file.txt") // "/home/user/file.txt"
   * pathJoin("relative", "/absolute") // "/absolute"
   */
  pathJoin(parts: string[]): string;
  /**
   * Get the parent directory of a path
   *
   * Returns empty string for root paths or paths without parent.
   * Does not resolve symlinks or check existence.
   * @param path - File or directory path
   * @example
   * pathDirname("/home/user/file.txt") // "/home/user"
   * pathDirname("/") // ""
   */
  pathDirname(path: string): string;
  /**
   * Get the final component of a path
   *
   * Returns empty string for root paths.
   * Does not strip file extension; use pathExtname for that.
   * @param path - File or directory path
   * @example
   * pathBasename("/home/user/file.txt") // "file.txt"
   * pathBasename("/home/user/") // "user"
   */
  pathBasename(path: string): string;
  /**
   * Get the file extension including the dot
   *
   * Returns empty string if no extension. Only returns the last extension
   * for files like "archive.tar.gz" (returns ".gz").
   * @param path - File path
   * @example
   * pathExtname("file.txt") // ".txt"
   * pathExtname("archive.tar.gz") // ".gz"
   * pathExtname("Makefile") // ""
   */
  pathExtname(path: string): string;
  /**
   * Check if a path is absolute
   *
   * On Unix: starts with "/". On Windows: starts with drive letter or UNC path.
   * @param path - Path to check
   */
  pathIsAbsolute(path: string): boolean;

  // === Event/Hook Operations ===
  /**
   * Subscribe to an editor event
   *
   * Handler must be a global function name (not a closure).
   * Multiple handlers can be registered for the same event.
   * Events: "buffer_save", "cursor_moved", "buffer_modified", etc.
   * @param event_name - Event to subscribe to
   * @param handler_name - Name of globalThis function to call with event data
   * @example
   * globalThis.onSave = (data) => {
   * editor.setStatus(`Saved: ${data.path}`);
   * };
   * editor.on("buffer_save", "onSave");
   */
  on(event_name: string, handler_name: string): boolean;
  /**
   * Unregister an event handler
   * @param event_name - Name of the event
   * @param handler_name - Name of the handler to remove
   * @returns true if handler was found and removed
   */
  off(event_name: string, handler_name: string): boolean;
  /**
   * Get list of registered handlers for an event
   * @param event_name - Name of the event
   * @returns Array of handler function names
   */
  getHandlers(event_name: string): string[];

  // === Virtual Buffer Operations ===
  /**
   * Create a virtual buffer in a new horizontal split below current pane
   *
   * Use for results panels, diagnostics, logs, etc. The panel_id enables
   * idempotent updates: if a panel with that ID exists, its content is replaced
   * instead of creating a new split. Define the mode with defineMode first.
   * @param options - Buffer configuration
   * @example
   * // First define the mode with keybindings
   * editor.defineMode("search-results", "special", [
   * ["Return", "search_goto"],
   * ["q", "close_buffer"]
   * ], true);
   *
   * // Then create the buffer
   * const id = await editor.createVirtualBufferInSplit({
   * name: "*Search*",
   * mode: "search-results",
   * read_only: true,
   * entries: [
   * { text: "src/main.rs:42: match\n", properties: { file: "src/main.rs", line: 42 } }
   * ],
   * ratio: 0.3,
   * panel_id: "search"
   * });
   */
  createVirtualBufferInSplit(options: CreateVirtualBufferOptions): Promise<CreateVirtualBufferResult>;
  /**
   * Create a virtual buffer in an existing split
   * @param options - Configuration for the virtual buffer
   * @returns Promise resolving to the buffer ID of the created virtual buffer
   */
  createVirtualBufferInExistingSplit(options: CreateVirtualBufferInExistingSplitOptions): Promise<number>;
  /**
   * Create a virtual buffer in the current split as a new tab
   * This is useful for help panels, documentation, etc. that should open
   * alongside other buffers rather than in a separate split.
   * @param options - Configuration for the virtual buffer
   * @returns Promise resolving to the buffer ID of the created virtual buffer
   */
  createVirtualBuffer(options: CreateVirtualBufferInCurrentSplitOptions): Promise<number>;
  /**
   * Define a buffer mode with keybindings
   * @param name - Mode name (e.g., "diagnostics-list")
   * @param parent - Parent mode name for inheritance (e.g., "special"), or null
   * @param bindings - Array of [key_string, command_name] pairs
   * @param read_only - Whether buffers in this mode are read-only
   * @returns true if mode was defined successfully
   * @example
   * editor.defineMode("diagnostics-list", "special", [
   * ["Return", "diagnostics_goto"],
   * ["q", "close_buffer"]
   * ], true);
   */
  defineMode(name: string, parent: string, bindings: [string, string][], read_only: boolean): boolean;
  /**
   * Switch the current split to display a buffer
   * @param buffer_id - ID of the buffer to show
   * @returns true if buffer was shown successfully
   */
  showBuffer(buffer_id: number): boolean;
  /**
   * Close a buffer and remove it from all splits
   * @param buffer_id - ID of the buffer to close
   * @returns true if buffer was closed successfully
   */
  closeBuffer(buffer_id: number): boolean;
  /**
   * Focus a specific split
   * @param split_id - ID of the split to focus
   * @returns true if split was focused successfully
   */
  focusSplit(split_id: number): boolean;
  /**
   * Set the buffer displayed in a specific split
   * @param split_id - ID of the split
   * @param buffer_id - ID of the buffer to display in the split
   * @returns true if the buffer was set successfully
   */
  setSplitBuffer(split_id: number, buffer_id: number): boolean;
  /**
   * Close a split (if not the last one)
   * @param split_id - ID of the split to close
   * @returns true if the split was closed successfully
   */
  closeSplit(split_id: number): boolean;
  /**
   * Get text properties at the cursor position in a buffer
   * @param buffer_id - ID of the buffer to query
   * @returns Array of property objects for text ranges containing the cursor
   * @example
   * const props = editor.getTextPropertiesAtCursor(bufferId);
   * if (props.length > 0 && props[0].location) {
   * editor.openFile(props[0].location.file, props[0].location.line, 0);
   * }
   */
  getTextPropertiesAtCursor(buffer_id: number): Record<string, unknown>[];
  /**
   * Set the content of a virtual buffer with text properties
   * @param buffer_id - ID of the virtual buffer
   * @param entries - Array of text entries with properties
   * @returns true if content was set successfully
   */
  setVirtualBufferContent(buffer_id: number, entries: TextPropertyEntry[]): boolean;

}
