
#ifndef BEATRAT_KEYBINDS_H
#define BEATRAT_KEYBINDS_H

#include <X11/Xlib.h>

/* Named actions, deliberately separate from raw keysyms -- the whole
 * point is that "what key does this" is configurable and "what does
 * this action do" isn't. Scoped for now to global/transport-level
 * actions; modal dialogs (Settings fields, Save/Export naming, the
 * file picker, confirm popups) keep their own hardcoded keys, since
 * those are dialog mechanics, not navigation/transport the way vim
 * bindings are about. Easy to grow this enum later without touching
 * the lookup mechanism itself. */
typedef enum {
    ACTION_NONE = 0,
    ACTION_QUIT,
    ACTION_SCRUB_LEFT,
    ACTION_SCRUB_RIGHT,
    ACTION_ZOOM_IN,
    ACTION_ZOOM_OUT,
    ACTION_PLAY_PAUSE,
    ACTION_RESTART,
    ACTION_VISUAL_SELECT,       /* 'v' -- start/extend a region selection */
    ACTION_VISUAL_LINE_SELECT,  /* Shift+v -- select the whole track/project at once */
    ACTION_CONFIRM_VISUAL,      /* Enter -- exit visual editing, keep the selection */
    ACTION_CANCEL_SELECTION,    /* Escape -- clear the selection entirely */
    ACTION_TOGGLE_LOOP_REGION,
    ACTION_TOGGLE_LOOP_ALL,
    ACTION_HIGHLIGHT_UP,   /* Up -- move the track highlight cursor up */
    ACTION_HIGHLIGHT_DOWN, /* Down -- move the track highlight cursor down */
    ACTION_HIGHLIGHT_PAGE_UP,   /* Page_Up -- move the cursor a whole screen's worth of tracks up */
    ACTION_HIGHLIGHT_PAGE_DOWN, /* Page_Down -- same, downward */
    ACTION_TOGGLE_NAV_CONTEXT, /* bare Tab -- switch what v/Shift+V/Escape/Up/Down act on */
    ACTION_JUMP_START, /* gg / Ctrl+A -- playhead (or cursor) to the very start */
    ACTION_JUMP_END,   /* G -- playhead (or cursor) to the very end */

    /* Copy/cut/void/paste -- act on whichever nav_context is currently
     * active (track-group/single-track selection in NAV_CONTEXT_TRACKS,
     * or the time-region selection in NAV_CONTEXT_PLAYBACK). Register
     * choice (beatrat vs. system) is baked into the action itself
     * rather than passed as a parameter, since the keybind table is
     * already a keysym+modifier -> action map -- see keybinds.c's
     * defaults for exactly which key+modifier combo reaches which of
     * these. ACTION_VOID has no register variant: voiding never touches
     * either register, by design (the vim 'x'-vs-'d' distinction). */
    ACTION_COPY_BEATRAT,
    ACTION_COPY_SYSTEM,
    ACTION_CUT_BEATRAT,
    ACTION_CUT_SYSTEM,
    ACTION_VOID,
    ACTION_PASTE_BEATRAT,
    ACTION_PASTE_SYSTEM,

    /* Interaction-mode switches (see AppMode in main.c). Escape always
     * returns to NORMAL from any other mode -- that reuses
     * ACTION_CANCEL_SELECTION rather than a dedicated action, so it
     * isn't listed here. FX/BEAT are backbone-only right now: entering
     * them just switches the mode indicator, nothing else. */
    ACTION_ENTER_INSERT_MODE,
    ACTION_ENTER_FX_MODE,
    ACTION_ENTER_BEAT_MODE,

    /* <leader>m -- toggles a pickup/move mode on/off (see main.c's
     * ACTION_TOGGLE_TRACK_MOVE handling); what it picks up depends on
     * nav_context. In NAV_CONTEXT_TRACKS it's "track move": Up/Down/
     * Page_Up/Page_Down reorder the picked-up track/group instead of
     * just moving the highlight cursor. In NAV_CONTEXT_PLAYBACK it's
     * "snippet move" instead: it picks up a single SNIPPET (a region
     * of one track's audio between two of its own insertion points)
     * and Up/Down swap it with its immediate neighbor. Either way,
     * pressing this again or Enter drops it in place, Escape cancels
     * and restores the original order/arrangement. */
    ACTION_TOGGLE_TRACK_MOVE,

    /* Bare 'm' in NORMAL mode -- mutes for PLAYBACK only (never
     * touches audio data, export, or anything else). No selection
     * active mutes every track; a track/group selection mutes just
     * those tracks. Pressing it again on an already-all-muted set
     * unmutes everything back -- see main.c's do_action() handling
     * for the exact toggle semantics. Deliberately a plain top-level
     * binding, not a leader chord -- unlike <leader>m (track/snippet
     * move, ACTION_TOGGLE_TRACK_MOVE above), this is meant to be a
     * fast, one-key transport control. */
    ACTION_TOGGLE_MUTE,

    ACTION_COUNT
} AppAction;

#define KEYBINDS_MAX 48 /* room for the built-in defaults plus a user's own additions */

typedef struct {
    KeySym keysym; /* NoSymbol here means "explicitly disabled" (config line was action=none) --
                    * kept as a real entry (not just omitted) so the per-version migration below
                    * knows the action was deliberately addressed and leaves it alone. */
    unsigned int modifiers; /* any combination of ControlMask/ShiftMask/Mod4Mask (Super)/Mod1Mask (Alt); 0 = no modifier */
    AppAction action;
} KeyBinding;

/* A leader chord is a single follow-up key pressed after the leader key
 * (LazyVim-style: <leader> then a key, for things beyond the normal
 * single-key bindings above). Only one level deep for now -- the data
 * shape doesn't prevent a deeper tree later, there's just nothing that
 * needs it yet. */
typedef struct {
    KeySym keysym; /* NoSymbol = explicitly disabled, same convention as KeyBinding above */
    unsigned int modifiers;
    AppAction action;
} LeaderChord;

#define KEYBINDS_MAX_LEADER_CHORDS 8

typedef struct {
    KeyBinding bindings[KEYBINDS_MAX];
    int num_bindings;
    KeySym leader_keysym;   /* NoSymbol = leader disabled */
    unsigned int leader_mods;
    LeaderChord leader_chords[KEYBINDS_MAX_LEADER_CHORDS];
    int num_leader_chords;
    char config_path[900]; /* resolved once in keybinds_load(), reused by keybinds_save() so
                             * callers (e.g. the in-app keybinds editor) don't need to re-derive it */
} KeyBindMap;

/* Loads ~/.config/beatrat/keybinds.conf if present. A line's absence is
 * still meaningful -- removing an action's line unbinds it, and
 * "action=none" unbinds it explicitly (distinct from just not mentioning
 * it -- see the version-migration note below). If the file doesn't
 * exist yet, writes the built-in defaults to disk first, commented, so
 * there's immediately something real to edit.
 *
 * Version migration: every action this build knows about is checked
 * against what the loaded file actually bound (or explicitly disabled
 * via "none"). Anything the file never mentions at all -- because it
 * was written by an older build that predates that action -- gets its
 * built-in default bindings added back in, and the file is rewritten
 * to include them. An action the file DOES mention, even as "none", is
 * left exactly as the user set it. This replaces needing a hand-written
 * one-off shim here every time a new action is added. */
void keybinds_load(KeyBindMap *kb);

/* Returns the bound action for this keysym+modifier combination, or
 * ACTION_NONE. Mask out things like NumLock/CapsLock from modifiers
 * before calling -- only bits keybinds.conf can actually express
 * (ControlMask, ShiftMask, Mod4Mask/Super, Mod1Mask/Alt) should be
 * passed in. */
AppAction keybinds_lookup(const KeyBindMap *kb, KeySym keysym, unsigned int modifiers);

/* Rewrites kb->config_path from the CURRENT in-memory state of kb
 * (not the built-in defaults) -- used after keybinds_load()'s own
 * migration pass, and by the in-app keybinds editor after a rebind.
 * Comments explaining individual actions are not preserved across a
 * save (the file is fully regenerated); the general format/modifier
 * explanation header always is. */
void keybinds_save(KeyBindMap *kb);

/* The config-file name for an action (e.g. ACTION_COPY_BEATRAT ->
 * "copy"), or "?" if somehow out of range. Used by the keybinds editor
 * and by keybinds_save(). */
const char *keybinds_action_name(AppAction action);

/* True for the handful of actions that are only ever reached as a
 * leader chord (<leader> then a key) rather than a top-level binding --
 * currently just FX/BEAT mode. Lets the editor know which list
 * (kb->bindings vs. kb->leader_chords) to search/rebind for a given
 * action, without hardcoding the two actions outside this file. */
int keybinds_action_is_leader_chord(AppAction action);

/* Writes a human-readable form of whatever is currently bound to
 * `action` into buf (e.g. "y, ctrl+c", "ctrl+shift+v", "(disabled)", or
 * "(unbound)" if the file never mentioned it and somehow wasn't
 * migrated in either). Multiple bindings for the same action are
 * comma-joined in the order they appear in the table. */
void keybinds_format_binding(const KeyBindMap *kb, AppAction action, char *buf, size_t buf_size);

/* Replaces every existing binding (or leader chord) for `action` with
 * exactly one: new_keysym/new_mods. Pass new_keysym = NoSymbol to
 * disable the action instead. If that key+modifier combo was already
 * claimed by a DIFFERENT action, that other binding is removed (the new
 * one wins, same as most keybind editors) and a one-line note is
 * written into conflict_msg (empty string if there was no conflict).
 * Saves to disk on success. Returns 0 on success, -1 if the table was
 * already full and couldn't fit the new entry (existing-action
 * removals always free at least one slot first, so this should only
 * happen if KEYBINDS_MAX/KEYBINDS_MAX_LEADER_CHORDS themselves need
 * raising). */
int keybinds_rebind(KeyBindMap *kb, AppAction action, KeySym new_keysym, unsigned int new_mods,
                     char *conflict_msg, size_t conflict_msg_size);

#endif

