---@mod claude-code.terminal Terminal management for claude-code.nvim
---@brief [[
--- This module provides terminal buffer management for claude-code.nvim.
--- It handles creating, toggling, and managing the terminal window.
---@brief ]]

local M = {}

--- Terminal buffer and window management
-- @table ClaudeCodeTerminal
-- @field instances table Key-value store of git root to buffer number
-- @field saved_updatetime number|nil Original updatetime before Claude Code was opened
-- @field current_instance string|nil Current git root path for active instance
M.terminal = {
  instances = {},
  saved_updatetime = nil,
  current_instance = nil,
}

--- Get the current git root or a fallback identifier
--- @param git table The git module
--- @return string identifier Git root path or fallback identifier
local function get_instance_identifier(git)
  local git_root = git.get_git_root()
  if git_root then
    return git_root
  else
    -- Fallback to current working directory if not in a git repo
    return vim.fn.getcwd()
  end
end

--- Calculate floating window dimensions from percentage strings
--- @param value number|string Dimension value (number or percentage string)
--- @param max_value number Maximum value (columns or lines)
--- @return number Calculated dimension
--- @private
local function calculate_float_dimension(value, max_value)
  if value == nil then
    return math.floor(max_value * 0.8) -- Default to 80% if not specified
  elseif type(value) == 'string' and value:match('^%d+%%$') then
    local percentage = tonumber(value:match('^(%d+)%%$'))
    return math.floor(max_value * percentage / 100)
  end
  return value
end

--- Calculate floating window position for centering
--- @param value number|string Position value (number, "center", or percentage)
--- @param window_size number Size of the window
--- @param max_value number Maximum value (columns or lines)
--- @return number Calculated position
--- @private
local function calculate_float_position(value, window_size, max_value)
  local pos
  if value == 'center' then
    pos = math.floor((max_value - window_size) / 2)
  elseif type(value) == 'string' and value:match('^%d+%%$') then
    local percentage = tonumber(value:match('^(%d+)%%$'))
    pos = math.floor(max_value * percentage / 100)
  else
    pos = value or 0
  end
  -- Clamp position to ensure window is visible
  return math.max(0, math.min(pos, max_value - window_size))
end

--- Create a floating window for Claude Code
--- @param config table Plugin configuration containing window settings
--- @param existing_bufnr number|nil Buffer number of existing buffer to show in the float (optional)
--- @return number Window ID of the created floating window
--- @private
local function create_float(config, existing_bufnr)
  local float_config = config.window.float or {}

  -- Get editor dimensions (accounting for command line, status line, etc.)
  local editor_width = vim.o.columns
  local editor_height = vim.o.lines - vim.o.cmdheight - 1 -- Subtract command line and status line

  -- Calculate dimensions
  local width = calculate_float_dimension(float_config.width, editor_width)
  local height = calculate_float_dimension(float_config.height, editor_height)

  -- Calculate position
  local row = calculate_float_position(float_config.row, height, editor_height)
  local col = calculate_float_position(float_config.col, width, editor_width)

  -- Create floating window configuration
  local win_config = {
    relative = float_config.relative or 'editor',
    width = width,
    height = height,
    row = row,
    col = col,
    border = float_config.border or 'rounded',
    style = 'minimal',
  }

  -- Create buffer if we don't have an existing one
  local bufnr = existing_bufnr
  if not bufnr then
    bufnr = vim.api.nvim_create_buf(false, true) -- unlisted, scratch
  else
    -- Validate existing buffer is still valid and a terminal
    if not vim.api.nvim_buf_is_valid(bufnr) then
      bufnr = vim.api.nvim_create_buf(false, true) -- unlisted, scratch
    else
      local buftype = vim.api.nvim_get_option_value('buftype', {buf = bufnr})
      if buftype ~= 'terminal' then
        -- Buffer exists but is no longer a terminal, create a new one
        bufnr = vim.api.nvim_create_buf(false, true) -- unlisted, scratch
      end
    end
  end

  -- Create and return the floating window
  return vim.api.nvim_open_win(bufnr, true, win_config)
end

--- Build command with git root directory if configured
--- @param config table Plugin configuration
--- @param git table Git module
--- @param base_cmd string Base command to run
--- @return string Command with git root directory change if applicable
--- @private
local function build_command_with_git_root(config, git, base_cmd)
  if config.git and config.git.use_git_root then
    local git_root = git.get_git_root()
    if git_root then
      local quoted_root = vim.fn.shellescape(git_root)
      -- Use configurable shell commands
      local separator = config.shell.separator
      local pushd_cmd = config.shell.pushd_cmd
      local popd_cmd = config.shell.popd_cmd
      return pushd_cmd
        .. ' '
        .. quoted_root
        .. ' '
        .. separator
        .. ' '
        .. base_cmd
        .. ' '
        .. separator
        .. ' '
        .. popd_cmd
    end
  end
  return base_cmd
end

--- Configure common window options
--- @param win_id number Window ID to configure
--- @param config table Plugin configuration
--- @private
local function configure_window_options(win_id, config)
  if config.window.hide_numbers then
    vim.api.nvim_set_option_value('number', false, {win = win_id})
    vim.api.nvim_set_option_value('relativenumber', false, {win = win_id})
  end

  if config.window.hide_signcolumn then
    vim.api.nvim_set_option_value('signcolumn', 'no', {win = win_id})
  end
end

--- Generate buffer name for instance
--- @param instance_id string Instance identifier
--- @param config table Plugin configuration
--- @return string Buffer name
--- @private
local function generate_buffer_name(instance_id, config)
  if config.git.multi_instance then
    return 'claude-code-' .. instance_id:gsub('[^%w%-_]', '-')
  else
    return 'claude-code'
  end
end

--- Create a split window according to the specified position configuration
--- @param position string Window position configuration
--- @param config table Plugin configuration containing window settings
--- @param existing_bufnr number|nil Buffer number of existing buffer to show in the split (optional)
--- @private
local function create_split(position, config, existing_bufnr)
  -- Handle floating window
  if position == 'float' then
    return create_float(config, existing_bufnr)
  end

  local is_vertical = position:match('vsplit') or position:match('vertical')

  -- Create the window with the user's specified command
  -- If the command already contains 'split', use it as is
  if position:match('split') then
    vim.cmd(position)
  else
    -- Otherwise append the appropriate split command
    local split_cmd = is_vertical and 'vsplit' or 'split'
    vim.cmd(position .. ' ' .. split_cmd)
  end

  -- If we have an existing buffer to display, switch to it
  if existing_bufnr then
    vim.cmd('buffer ' .. existing_bufnr)
  end

  -- Resize the window appropriately based on split type
  if is_vertical then
    vim.cmd('vertical resize ' .. math.floor(vim.o.columns * config.window.split_ratio))
  else
    vim.cmd('resize ' .. math.floor(vim.o.lines * config.window.split_ratio))
  end
end

--- Set up function to force insert mode when entering the Claude Code window
--- @param claude_code table The main plugin module
--- @param config table The plugin configuration
function M.force_insert_mode(claude_code, config)
  local current_bufnr = vim.fn.bufnr('%')

  -- Check if current buffer is any of our Claude instances
  local is_claude_instance = false
  for _, bufnr in pairs(claude_code.claude_code.instances) do
    if bufnr and bufnr == current_bufnr and vim.api.nvim_buf_is_valid(bufnr) then
      is_claude_instance = true
      break
    end
  end

  if is_claude_instance then
    -- Only enter insert mode if we're in the terminal buffer and not already in insert mode
    -- and not configured to stay in normal mode
    if config.window.start_in_normal_mode then
      return
    end

    local mode = vim.api.nvim_get_mode().mode
    if vim.bo.buftype == 'terminal' and mode ~= 't' and mode ~= 'i' then
      vim.cmd 'silent! stopinsert'
      vim.schedule(function()
        vim.cmd 'silent! startinsert'
      end)
    end
  end
end

--- Determine instance ID based on configuration
--- @param config table Plugin configuration
--- @param git table Git module
--- @return string instance_id Instance identifier
--- @private
local function get_instance_id(config, git)
  if config.git.multi_instance then
    if config.git.use_git_root then
      return get_instance_identifier(git)
    else
      return vim.fn.getcwd()
    end
  else
    -- Use a fixed ID for single instance mode
    return 'global'
  end
end

--- Check if buffer is a valid terminal
--- @param bufnr number Buffer number
--- @return boolean is_valid True if buffer is a valid terminal
--- @private

--- Handle existing instance (toggle visibility)
--- @param bufnr number Buffer number
--- @param config table Plugin configuration
--- @private
local function handle_existing_instance(bufnr, config)
  local win_ids = vim.fn.win_findbuf(bufnr)
  if #win_ids > 0 then
    -- Claude Code is visible, close the window
    for _, win_id in ipairs(win_ids) do
      vim.api.nvim_win_close(win_id, true)
    end
  else
    -- Claude Code buffer exists but is not visible, open it in a split or float
    if config.window.position == 'float' then
      create_float(config, bufnr)
    else
      create_split(config.window.position, config, bufnr)
    end
    -- Force insert mode more aggressively unless configured to start in normal mode
    if not config.window.start_in_normal_mode then
      vim.schedule(function()
        vim.cmd 'stopinsert | startinsert'
      end)
    end
  end
end

--- Create new Claude Code instance
--- @param claude_code table The main plugin module
--- @param config table Plugin configuration
--- @param git table Git module
--- @param instance_id string Instance identifier
--- @private
local function create_new_instance(claude_code, config, git, instance_id)
  if config.window.position == 'float' then
    -- For floating window, create buffer first with terminal
    local new_bufnr = vim.api.nvim_create_buf(false, true) -- unlisted, scratch
    vim.api.nvim_set_option_value('bufhidden', 'hide', {buf = new_bufnr})

    -- Create the floating window
    local win_id = create_float(config, new_bufnr)

    -- Set current buffer to run terminal command
    vim.api.nvim_win_set_buf(win_id, new_bufnr)

    -- Determine command
    local cmd = build_command_with_git_root(config, git, config.command)

    -- Run terminal in the buffer
    vim.fn.termopen(cmd)

    -- Create a unique buffer name
    local buffer_name = generate_buffer_name(instance_id, config)
    vim.api.nvim_buf_set_name(new_bufnr, buffer_name)

    -- Configure window options
    configure_window_options(win_id, config)

    -- Store buffer number for this instance
    claude_code.claude_code.instances[instance_id] = new_bufnr

    -- Enter insert mode if configured
    if config.window.enter_insert and not config.window.start_in_normal_mode then
      vim.cmd 'startinsert'
    end
  else
    -- Regular split window
    create_split(config.window.position, config)

    -- Determine if we should use the git root directory
    local base_cmd = build_command_with_git_root(config, git, config.command)
    local cmd = 'terminal ' .. base_cmd

    vim.cmd(cmd)
    vim.cmd 'setlocal bufhidden=hide'

    -- Create a unique buffer name
    local buffer_name = generate_buffer_name(instance_id, config)
    vim.cmd('file ' .. buffer_name)

    -- Configure window options using helper function
    local current_win = vim.api.nvim_get_current_win()
    configure_window_options(current_win, config)

    -- Store buffer number for this instance
    claude_code.claude_code.instances[instance_id] = vim.fn.bufnr('%')

    -- Automatically enter insert mode in terminal unless configured to start in normal mode
    if config.window.enter_insert and not config.window.start_in_normal_mode then
      vim.cmd 'startinsert'
    end
  end
end

--- Toggle the Claude Code terminal window
--- @param claude_code table The main plugin module
--- @param config table The plugin configuration
--- @param git table The git module
function M.toggle(claude_code, config, git)
  -- Determine instance ID based on config
  local instance_id = get_instance_id(config, git)
  claude_code.claude_code.current_instance = instance_id

  -- Check if this Claude Code instance is already running
  local bufnr = claude_code.claude_code.instances[instance_id]

  -- Validate existing buffer
  if bufnr and not is_valid_terminal_buffer(bufnr) then
    -- Buffer is no longer a valid terminal, reset
    claude_code.claude_code.instances[instance_id] = nil
    bufnr = nil
  end

  if bufnr and vim.api.nvim_buf_is_valid(bufnr) then
    -- Handle existing instance (toggle visibility)
    handle_existing_instance(bufnr, config)
  else
    -- Prune invalid buffer entries
    if bufnr and not vim.api.nvim_buf_is_valid(bufnr) then
      claude_code.claude_code.instances[instance_id] = nil
    end
    -- Create new instance
    create_new_instance(claude_code, config, git, instance_id)
  end
end

return M

local function is_valid_terminal_buffer(bufnr)
  if not bufnr or not vim.api.nvim_buf_is_valid(bufnr) then
    return false
  end
  local buftype = nil
  pcall(function()
    buftype = vim.api.nvim_get_option_value('buftype', {buf = bufnr})
  end)
  local terminal_job_id = nil
  pcall(function()
    terminal_job_id = vim.b[bufnr].terminal_job_id
  end)
  return buftype == 'terminal'
    and terminal_job_id
    and vim.fn.jobwait({ terminal_job_id }, 0)[1] == -1
end

---@mod claude-code.config Configuration management for claude-code.nvim
---@brief [[
--- This module handles configuration management and validation for claude-code.nvim.
--- It provides the default configuration, validation, and merging of user config.
---@brief ]]

local M = {}

--- ClaudeCodeWindow class for window configuration
-- @table ClaudeCodeWindow
-- @field split_ratio number Percentage of screen for the terminal window (height for horizontal, width for vertical)
-- @field position string Position of the window: "botright", "topleft", "vertical", "float" etc.
-- @field enter_insert boolean Whether to enter insert mode when opening Claude Code
-- @field start_in_normal_mode boolean Whether to start in normal mode instead of insert mode when opening Claude Code
-- @field hide_numbers boolean Hide line numbers in the terminal window
-- @field hide_signcolumn boolean Hide the sign column in the terminal window
-- @field float table|nil Floating window configuration (only used when position is "float")
-- @field float.width number|string Width of floating window (number: columns, string: percentage like "80%")
-- @field float.height number|string Height of floating window (number: rows, string: percentage like "80%")
-- @field float.row number|string|nil Row position (number: absolute, string: "center" or percentage)
-- @field float.col number|string|nil Column position (number: absolute, string: "center" or percentage)
-- @field float.border string Border style: "none", "single", "double", "rounded", "solid", "shadow", or array
-- @field float.relative string Relative positioning: "editor" or "cursor"

--- ClaudeCodeRefresh class for file refresh configuration
-- @table ClaudeCodeRefresh
-- @field enable boolean Enable file change detection
-- @field updatetime number updatetime when Claude Code is active (milliseconds)
-- @field timer_interval number How often to check for file changes (milliseconds)
-- @field show_notifications boolean Show notification when files are reloaded

--- ClaudeCodeGit class for git integration configuration
-- @table ClaudeCodeGit
-- @field use_git_root boolean Set CWD to git root when opening Claude Code (if in git project)
-- @field multi_instance boolean Use multiple Claude instances (one per git root)

--- ClaudeCodeKeymapsToggle class for toggle keymap configuration
-- @table ClaudeCodeKeymapsToggle
-- @field normal string|boolean Normal mode keymap for toggling Claude Code, false to disable
-- @field terminal string|boolean Terminal mode keymap for toggling Claude Code, false to disable

--- ClaudeCodeKeymaps class for keymap configuration
-- @table ClaudeCodeKeymaps
-- @field toggle ClaudeCodeKeymapsToggle Keymaps for toggling Claude Code
-- @field window_navigation boolean Enable window navigation keymaps
-- @field scrolling boolean Enable scrolling keymaps

--- ClaudeCodeCommandVariants class for command variant configuration
-- @table ClaudeCodeCommandVariants
-- Conversation management:
-- @field continue string|boolean Resume the most recent conversation
-- @field resume string|boolean Display an interactive conversation picker
-- Output options:
-- @field verbose string|boolean Enable verbose logging with full turn-by-turn output
-- Additional options can be added as needed

--- ClaudeCodeShell class for shell configuration
-- @table ClaudeCodeShell
-- @field separator string Command separator used in shell commands (e.g., '&&', ';', '|')
-- @field pushd_cmd string Command to push directory onto stack (e.g., 'pushd' for bash/zsh)
-- @field popd_cmd string Command to pop directory from stack (e.g., 'popd' for bash/zsh)

--- ClaudeCodeConfig class for main configuration
-- @table ClaudeCodeConfig
-- @field window ClaudeCodeWindow Terminal window settings
-- @field refresh ClaudeCodeRefresh File refresh settings
-- @field git ClaudeCodeGit Git integration settings
-- @field shell ClaudeCodeShell Shell-specific configuration
-- @field command string Command used to launch Claude Code
-- @field command_variants ClaudeCodeCommandVariants Command variants configuration
-- @field keymaps ClaudeCodeKeymaps Keymaps configuration

--- Default configuration options
--- @type ClaudeCodeConfig
M.default_config = {
  -- Terminal window settings
  window = {
    split_ratio = 0.3, -- Percentage of screen for the terminal window (height or width)
    height_ratio = 0.3, -- DEPRECATED: Use split_ratio instead
    position = 'botright', -- Position of the window: "botright", "topleft", "vertical", "float", etc.
    enter_insert = true, -- Whether to enter insert mode when opening Claude Code
    start_in_normal_mode = false, -- Whether to start in normal mode instead of insert mode
    hide_numbers = true, -- Hide line numbers in the terminal window
    hide_signcolumn = true, -- Hide the sign column in the terminal window
    -- Default floating window configuration
    float = {
      width = '80%', -- Width as percentage of editor
      height = '80%', -- Height as percentage of editor
      row = 'center', -- Center vertically
      col = 'center', -- Center horizontally
      relative = 'editor', -- Position relative to editor
      border = 'rounded', -- Border style
    },
  },
  -- File refresh settings
  refresh = {
    enable = true, -- Enable file change detection
    updatetime = 100, -- updatetime to use when Claude Code is active (milliseconds)
    timer_interval = 1000, -- How often to check for file changes (milliseconds)
    show_notifications = true, -- Show notification when files are reloaded
  },
  -- Git integration settings
  git = {
    use_git_root = true, -- Set CWD to git root when opening Claude Code (if in git project)
    multi_instance = true, -- Use multiple Claude instances (one per git root)
  },
  -- Shell-specific settings
  shell = {
    separator = '&&', -- Command separator used in shell commands
    pushd_cmd = 'pushd', -- Command to push directory onto stack
    popd_cmd = 'popd', -- Command to pop directory from stack
  },
  -- Command settings
  command = 'claude', -- Command used to launch Claude Code
  -- Command variants
  command_variants = {
    -- Conversation management
    continue = '--continue', -- Resume the most recent conversation
    resume = '--resume', -- Display an interactive conversation picker

    -- Output options
    verbose = '--verbose', -- Enable verbose logging with full turn-by-turn output
  },
  -- Keymaps
  keymaps = {
    toggle = {
      normal = '<C-,>', -- Normal mode keymap for toggling Claude Code
      terminal = '<C-,>', -- Terminal mode keymap for toggling Claude Code
      variants = {
        continue = '<leader>cC', -- Normal mode keymap for Claude Code with continue flag
        verbose = '<leader>cV', -- Normal mode keymap for Claude Code with verbose flag
      },
    },
    window_navigation = true, -- Enable window navigation keymaps (<C-h/j/k/l>)
    scrolling = true, -- Enable scrolling keymaps (<C-f/b>) for page up/down
  },
}

--- Validate the configuration
--- Validate window configuration
--- @param window table Window configuration
--- @return boolean valid
--- @return string? error_message
local function validate_window_config(window)
  if type(window) ~= 'table' then
    return false, 'window config must be a table'
  end

  if type(window.split_ratio) ~= 'number' or window.split_ratio <= 0 or window.split_ratio > 1 then
    return false, 'window.split_ratio must be a number between 0 and 1'
  end

  if type(window.position) ~= 'string' then
    return false, 'window.position must be a string'
  end

  if type(window.enter_insert) ~= 'boolean' then
    return false, 'window.enter_insert must be a boolean'
  end

  if type(window.start_in_normal_mode) ~= 'boolean' then
    return false, 'window.start_in_normal_mode must be a boolean'
  end

  if type(window.hide_numbers) ~= 'boolean' then
    return false, 'window.hide_numbers must be a boolean'
  end

  if type(window.hide_signcolumn) ~= 'boolean' then
    return false, 'window.hide_signcolumn must be a boolean'
  end

  return true, nil
end

--- Validate floating window configuration
--- @param float table Float configuration
--- @return boolean valid
--- @return string? error_message
local function validate_float_config(float)
  if type(float) ~= 'table' then
    return false, 'window.float must be a table when position is "float"'
  end

  -- Validate width (can be number or percentage string)
  if type(float.width) == 'string' then
    if not float.width:match('^%d+%%$') then
      return false, 'window.float.width must be a number or percentage (e.g., "80%")'
    end
  elseif type(float.width) ~= 'number' or float.width <= 0 then
    return false, 'window.float.width must be a positive number or percentage string'
  end

  -- Validate height (can be number or percentage string)
  if type(float.height) == 'string' then
    if not float.height:match('^%d+%%$') then
      return false, 'window.float.height must be a number or percentage (e.g., "80%")'
    end
  elseif type(float.height) ~= 'number' or float.height <= 0 then
    return false, 'window.float.height must be a positive number or percentage string'
  end

  -- Validate relative (must be "editor" or "cursor")
  if float.relative ~= 'editor' and float.relative ~= 'cursor' then
    return false, 'window.float.relative must be "editor" or "cursor"'
  end

  -- Validate border (must be valid border style)
  local valid_borders = { 'none', 'single', 'double', 'rounded', 'solid', 'shadow' }
  local is_valid_border = false
  for _, border in ipairs(valid_borders) do
    if float.border == border then
      is_valid_border = true
      break
    end
  end
  -- Also allow array borders
  if not is_valid_border and type(float.border) ~= 'table' then
    return false,
      'window.float.border must be one of: none, single, double, rounded, solid, shadow, or an array'
  end

  -- Validate row and col if they exist
  if float.row ~= nil then
    if type(float.row) == 'string' and float.row ~= 'center' then
      if not float.row:match('^%d+%%$') then
        return false, 'window.float.row must be a number, "center", or percentage string'
      end
    elseif type(float.row) ~= 'number' and float.row ~= 'center' then
      return false, 'window.float.row must be a number, "center", or percentage string'
    end
  end

  if float.col ~= nil then
    if type(float.col) == 'string' and float.col ~= 'center' then
      if not float.col:match('^%d+%%$') then
        return false, 'window.float.col must be a number, "center", or percentage string'
      end
    elseif type(float.col) ~= 'number' and float.col ~= 'center' then
      return false, 'window.float.col must be a number, "center", or percentage string'
    end
  end

  return true, nil
end

--- Validate refresh configuration
--- @param refresh table Refresh configuration
--- @return boolean valid
--- @return string? error_message
local function validate_refresh_config(refresh)
  if type(refresh) ~= 'table' then
    return false, 'refresh config must be a table'
  end

  if type(refresh.enable) ~= 'boolean' then
    return false, 'refresh.enable must be a boolean'
  end

  if type(refresh.updatetime) ~= 'number' or refresh.updatetime <= 0 then
    return false, 'refresh.updatetime must be a positive number'
  end

  if type(refresh.timer_interval) ~= 'number' or refresh.timer_interval <= 0 then
    return false, 'refresh.timer_interval must be a positive number'
  end

  if type(refresh.show_notifications) ~= 'boolean' then
    return false, 'refresh.show_notifications must be a boolean'
  end

  return true, nil
end

--- Validate git configuration
--- @param git table Git configuration
--- @return boolean valid
--- @return string? error_message
local function validate_git_config(git)
  if type(git) ~= 'table' then
    return false, 'git config must be a table'
  end

  if type(git.use_git_root) ~= 'boolean' then
    return false, 'git.use_git_root must be a boolean'
  end

  if type(git.multi_instance) ~= 'boolean' then
    return false, 'git.multi_instance must be a boolean'
  end

  return true, nil
end

--- Validate shell configuration
--- @param shell table Shell configuration
--- @return boolean valid
--- @return string? error_message
local function validate_shell_config(shell)
  if type(shell) ~= 'table' then
    return false, 'shell config must be a table'
  end

  if type(shell.separator) ~= 'string' then
    return false, 'shell.separator must be a string'
  end

  if type(shell.pushd_cmd) ~= 'string' then
    return false, 'shell.pushd_cmd must be a string'
  end

  if type(shell.popd_cmd) ~= 'string' then
    return false, 'shell.popd_cmd must be a string'
  end

  return true, nil
end

--- Validate keymaps configuration
--- @param keymaps table Keymaps configuration
--- @return boolean valid
--- @return string? error_message
local function validate_keymaps_config(keymaps)
  if type(keymaps) ~= 'table' then
    return false, 'keymaps config must be a table'
  end

  if type(keymaps.toggle) ~= 'table' then
    return false, 'keymaps.toggle must be a table'
  end

  if not (keymaps.toggle.normal == false or type(keymaps.toggle.normal) == 'string') then
    return false, 'keymaps.toggle.normal must be a string or false'
  end

  if not (keymaps.toggle.terminal == false or type(keymaps.toggle.terminal) == 'string') then
    return false, 'keymaps.toggle.terminal must be a string or false'
  end

  -- Validate variant keymaps if they exist
  if keymaps.toggle.variants then
    if type(keymaps.toggle.variants) ~= 'table' then
      return false, 'keymaps.toggle.variants must be a table'
    end

    -- Check each variant keymap
    for variant_name, keymap in pairs(keymaps.toggle.variants) do
      if not (keymap == false or type(keymap) == 'string') then
        return false, 'keymaps.toggle.variants.' .. variant_name .. ' must be a string or false'
      end
    end
  end

  if type(keymaps.window_navigation) ~= 'boolean' then
    return false, 'keymaps.window_navigation must be a boolean'
  end

  if type(keymaps.scrolling) ~= 'boolean' then
    return false, 'keymaps.scrolling must be a boolean'
  end

  return true, nil
end

--- Validate command variants configuration
--- @param command_variants table Command variants configuration
--- @return boolean valid
--- @return string? error_message
local function validate_command_variants_config(command_variants)
  if type(command_variants) ~= 'table' then
    return false, 'command_variants config must be a table'
  end

  -- Check each command variant
  for variant_name, variant_args in pairs(command_variants) do
    if not (variant_args == false or type(variant_args) == 'string') then
      return false, 'command_variants.' .. variant_name .. ' must be a string or false'
    end
  end

  return true, nil
end

--- Validate configuration options
--- @param config ClaudeCodeConfig
--- @return boolean valid
--- @return string? error_message
local function validate_config(config)
  -- Validate window settings
  local valid, err = validate_window_config(config.window)
  if not valid then
    return false, err
  end

  -- Validate float configuration if position is "float"
  if config.window.position == 'float' then
    valid, err = validate_float_config(config.window.float)
    if not valid then
      return false, err
    end
  end

  -- Validate refresh settings
  valid, err = validate_refresh_config(config.refresh)
  if not valid then
    return false, err
  end

  -- Validate git settings
  valid, err = validate_git_config(config.git)
  if not valid then
    return false, err
  end

  -- Validate shell settings
  valid, err = validate_shell_config(config.shell)
  if not valid then
    return false, err
  end

  -- Validate command settings
  if type(config.command) ~= 'string' then
    return false, 'command must be a string'
  end

  -- Validate command variants settings
  valid, err = validate_command_variants_config(config.command_variants)
  if not valid then
    return false, err
  end

  -- Validate keymaps settings
  valid, err = validate_keymaps_config(config.keymaps)
  if not valid then
    return false, err
  end

  -- Cross-validate keymaps with command variants
  if config.keymaps.toggle.variants then
    for variant_name, keymap in pairs(config.keymaps.toggle.variants) do
      -- Ensure variant exists in command_variants
      if keymap ~= false and not config.command_variants[variant_name] then
        return false,
          'keymaps.toggle.variants.' .. variant_name .. ' has no corresponding command variant'
      end
    end
  end

  return true, nil
end

--- Parse user configuration and merge with defaults
--- @param user_config? table
--- @param silent? boolean Set to true to suppress error notifications (for tests)
--- @return ClaudeCodeConfig
function M.parse_config(user_config, silent)
  -- Handle backward compatibility first
  if user_config and user_config.window then
    if user_config.window.height_ratio and not user_config.window.split_ratio then
      -- Copy height_ratio to split_ratio for backward compatibility
      user_config.window.split_ratio = user_config.window.height_ratio
    end
  end

  local config = vim.tbl_deep_extend('force', {}, M.default_config, user_config or {})

  -- If position is float and no float config provided, use default float config
  if
    config.window.position == 'float'
    and not (user_config and user_config.window and user_config.window.float)
  then
    config.window.float = vim.deepcopy(M.default_config.window.float)
  end

  local valid, err = validate_config(config)
  if not valid then
    -- Only notify if not in silent mode
    if not silent then
      vim.notify('Claude Code: ' .. err, vim.log.levels.ERROR)
    end
    -- Fall back to default config in case of error
    return vim.deepcopy(M.default_config)
  end

  return config
end

return M

---@mod claude-code Claude Code Neovim Integration
---@brief [[
--- A plugin for seamless integration between Claude Code AI assistant and Neovim.
--- This plugin provides a terminal-based interface to Claude Code within Neovim.
---
--- Requirements:
--- - Neovim 0.7.0 or later
--- - Claude Code CLI tool installed and available in PATH
--- - plenary.nvim (dependency for git operations)
---
--- Usage:
--- ```lua
--- require('claude-code').setup({
---   -- Configuration options (optional)
--- })
--- ```
---@brief ]]

-- Import modules
local config = require('claude-code.config')
local commands = require('claude-code.commands')
local keymaps = require('claude-code.keymaps')
local file_refresh = require('claude-code.file_refresh')
local terminal = require('claude-code.terminal')
local git = require('claude-code.git')
local version = require('claude-code.version')

local M = {}

-- Make imported modules available
M.commands = commands

-- Store the current configuration
--- @type table
M.config = {}

-- Terminal buffer and window management
--- @type table
M.claude_code = terminal.terminal

--- Force insert mode when entering the Claude Code window
--- This is a public function used in keymaps
function M.force_insert_mode()
  terminal.force_insert_mode(M, M.config)
end

--- Get the current active buffer number
--- @return number|nil bufnr Current Claude instance buffer number or nil
local function get_current_buffer_number()
  -- Get current instance from the instances table
  local current_instance = M.claude_code.current_instance
  if current_instance and type(M.claude_code.instances) == 'table' then
    return M.claude_code.instances[current_instance]
  end
  return nil
end

--- Toggle the Claude Code terminal window
--- This is a public function used by commands
function M.toggle()
  terminal.toggle(M, M.config, git)

  -- Set up terminal navigation keymaps after toggling
  local bufnr = get_current_buffer_number()
  if bufnr and vim.api.nvim_buf_is_valid(bufnr) then
    keymaps.setup_terminal_navigation(M, M.config)
  end
end

--- Toggle the Claude Code terminal window with a specific command variant
--- @param variant_name string The name of the command variant to use
function M.toggle_with_variant(variant_name)
  if not variant_name or not M.config.command_variants[variant_name] then
    -- If variant doesn't exist, fall back to regular toggle
    return M.toggle()
  end

  -- Store the original command
  local original_command = M.config.command

  -- Set the command with the variant args
  M.config.command = original_command .. ' ' .. M.config.command_variants[variant_name]

  -- Call the toggle function with the modified command
  terminal.toggle(M, M.config, git)

  -- Set up terminal navigation keymaps after toggling
  local bufnr = get_current_buffer_number()
  if bufnr and vim.api.nvim_buf_is_valid(bufnr) then
    keymaps.setup_terminal_navigation(M, M.config)
  end

  -- Restore the original command
  M.config.command = original_command
end

--- Get the current version of the plugin
--- @return string version Current version string
function M.get_version()
  return version.string()
end

--- Version information
M.version = version

--- Setup function for the plugin
--- @param user_config? table User configuration table (optional)
function M.setup(user_config)
  -- Parse and validate configuration
  -- Don't use silent mode for regular usage - users should see config errors
  M.config = config.parse_config(user_config, false)

  -- Set up autoread option
  vim.o.autoread = true

  -- Set up file refresh functionality
  file_refresh.setup(M, M.config)

  -- Register commands
  commands.register_commands(M)

  -- Register keymaps
  keymaps.register_keymaps(M, M.config)
end

return M

---@mod claude-code.file_refresh File refresh functionality for claude-code.nvim
---@brief [[
--- This module provides file refresh functionality to detect and reload files
--- that have been modified by Claude Code or other external processes.
---@brief ]]

local M = {}

--- Timer for checking file changes
--- @type userdata|nil
local refresh_timer = nil

--- Setup autocommands for file change detection
--- @param claude_code table The main plugin module
--- @param config table The plugin configuration
function M.setup(claude_code, config)
  if not config.refresh.enable then
    return
  end

  local augroup = vim.api.nvim_create_augroup('ClaudeCodeFileRefresh', { clear = true })

  -- Create an autocommand that checks for file changes more frequently
  vim.api.nvim_create_autocmd({
    'CursorHold',
    'CursorHoldI',
    'FocusGained',
    'BufEnter',
    'InsertLeave',
    'TextChanged',
    'TermLeave',
    'TermEnter',
    'BufWinEnter',
  }, {
    group = augroup,
    pattern = '*',
    callback = function()
      if vim.fn.filereadable(vim.fn.expand '%') == 1 then
        vim.cmd 'checktime'
      end
    end,
    desc = 'Check for file changes on disk',
  })

  -- Clean up any existing timer
  if refresh_timer then
    refresh_timer:stop()
    refresh_timer:close()
    refresh_timer = nil
  end

  -- Create a timer to check for file changes periodically
  refresh_timer = vim.loop.new_timer()
  if refresh_timer then
    refresh_timer:start(
      0,
      config.refresh.timer_interval,
      vim.schedule_wrap(function()
        -- Only check time if there's an active Claude Code terminal
        local current_instance = claude_code.claude_code.current_instance
        local bufnr = current_instance and claude_code.claude_code.instances[current_instance]
        if bufnr and vim.api.nvim_buf_is_valid(bufnr) and #vim.fn.win_findbuf(bufnr) > 0 then
          vim.cmd 'silent! checktime'
        end
      end)
    )
  end

  -- Create an autocommand that notifies when a file has been changed externally
  if config.refresh.show_notifications then
    vim.api.nvim_create_autocmd('FileChangedShellPost', {
      group = augroup,
      pattern = '*',
      callback = function()
        vim.notify('File changed on disk. Buffer reloaded.', vim.log.levels.INFO)
      end,
      desc = 'Notify when a file is changed externally',
    })
  end

  -- Set a shorter updatetime while Claude Code is open
  claude_code.claude_code.saved_updatetime = vim.o.updatetime

  -- When Claude Code opens, set a shorter updatetime
  vim.api.nvim_create_autocmd('TermOpen', {
    group = augroup,
    pattern = '*',
    callback = function()
      local buf = vim.api.nvim_get_current_buf()
      local buf_name = vim.api.nvim_buf_get_name(buf)
      if buf_name:match('claude%-code$') then
        claude_code.claude_code.saved_updatetime = vim.o.updatetime
        vim.o.updatetime = config.refresh.updatetime
      end
    end,
    desc = 'Set shorter updatetime when Claude Code is open',
  })

  -- When Claude Code closes, restore normal updatetime
  vim.api.nvim_create_autocmd('TermClose', {
    group = augroup,
    pattern = '*',
    callback = function()
      local buf_name = vim.api.nvim_buf_get_name(0)
      if buf_name:match('claude%-code$') then
        vim.o.updatetime = claude_code.claude_code.saved_updatetime
      end
    end,
    desc = 'Restore normal updatetime when Claude Code is closed',
  })
end

--- Clean up the file refresh functionality (stop the timer)
function M.cleanup()
  if refresh_timer then
    refresh_timer:stop()
    refresh_timer:close()
    refresh_timer = nil
  end
end

return M

---@mod claude-code.keymaps Keymap management for claude-code.nvim
---@brief [[
--- This module provides keymap registration and handling for claude-code.nvim.
--- It handles normal mode, terminal mode, and window navigation keymaps.
---@brief ]]

local M = {}

--- Register keymaps for claude-code.nvim
--- @param claude_code table The main plugin module
--- @param config table The plugin configuration
function M.register_keymaps(claude_code, config)
  local map_opts = { noremap = true, silent = true }

  -- Normal mode toggle keymaps
  if config.keymaps.toggle.normal then
    vim.api.nvim_set_keymap(
      'n',
      config.keymaps.toggle.normal,
      [[<cmd>ClaudeCode<CR>]],
      vim.tbl_extend('force', map_opts, { desc = 'Claude Code: Toggle' })
    )
  end

  if config.keymaps.toggle.terminal then
    -- Terminal mode toggle keymap
    -- In terminal mode, special keys like Ctrl need different handling
    -- We use a direct escape sequence approach for more reliable terminal mappings
    vim.api.nvim_set_keymap(
      't',
      config.keymaps.toggle.terminal,
      [[<C-\><C-n>:ClaudeCode<CR>]],
      vim.tbl_extend('force', map_opts, { desc = 'Claude Code: Toggle' })
    )
  end

  -- Register variant keymaps if configured
  if config.keymaps.toggle.variants then
    for variant_name, keymap in pairs(config.keymaps.toggle.variants) do
      if keymap then
        -- Convert variant name to PascalCase for command name (e.g., "continue" -> "Continue")
        local capitalized_name = variant_name:gsub('^%l', string.upper)
        local cmd_name = 'ClaudeCode' .. capitalized_name

        vim.api.nvim_set_keymap(
          'n',
          keymap,
          string.format([[<cmd>%s<CR>]], cmd_name),
          vim.tbl_extend('force', map_opts, { desc = 'Claude Code: ' .. capitalized_name })
        )
      end
    end
  end

  -- Register with which-key if it's available
  vim.defer_fn(function()
    local status_ok, which_key = pcall(require, 'which-key')
    if status_ok then
      if config.keymaps.toggle.normal then
        which_key.add {
          mode = 'n',
          { config.keymaps.toggle.normal, desc = 'Claude Code: Toggle', icon = '🤖' },
        }
      end
      if config.keymaps.toggle.terminal then
        which_key.add {
          mode = 't',
          { config.keymaps.toggle.terminal, desc = 'Claude Code: Toggle', icon = '🤖' },
        }
      end

      -- Register variant keymaps with which-key
      if config.keymaps.toggle.variants then
        for variant_name, keymap in pairs(config.keymaps.toggle.variants) do
          if keymap then
            local capitalized_name = variant_name:gsub('^%l', string.upper)
            which_key.add {
              mode = 'n',
              { keymap, desc = 'Claude Code: ' .. capitalized_name, icon = '🤖' },
            }
          end
        end
      end
    end
  end, 100)
end

--- Set up terminal-specific keymaps for window navigation
--- @param claude_code table The main plugin module
--- @param config table The plugin configuration
function M.setup_terminal_navigation(claude_code, config)
  -- Get current active Claude instance buffer
  local current_instance = claude_code.claude_code.current_instance
  local buf = current_instance and claude_code.claude_code.instances[current_instance]
  if buf and vim.api.nvim_buf_is_valid(buf) then
    -- Create autocommand to enter insert mode when the terminal window gets focus
    local augroup = vim.api.nvim_create_augroup('ClaudeCodeTerminalFocus_' .. buf, { clear = true })

    -- Set up multiple events for more reliable focus detection
    vim.api.nvim_create_autocmd(
      { 'WinEnter', 'BufEnter', 'WinLeave', 'FocusGained', 'CmdLineLeave' },
      {
        group = augroup,
        callback = function()
          vim.schedule(claude_code.force_insert_mode)
        end,
        desc = 'Auto-enter insert mode when focusing Claude Code terminal',
      }
    )

    -- Window navigation keymaps
    if config.keymaps.window_navigation then
      -- Window navigation keymaps with special handling to force insert mode in the target window
      vim.api.nvim_buf_set_keymap(
        buf,
        't',
        '<C-h>',
        [[<C-\><C-n><C-w>h:lua require("claude-code").force_insert_mode()<CR>]],
        { noremap = true, silent = true, desc = 'Window: move left' }
      )
      vim.api.nvim_buf_set_keymap(
        buf,
        't',
        '<C-j>',
        [[<C-\><C-n><C-w>j:lua require("claude-code").force_insert_mode()<CR>]],
        { noremap = true, silent = true, desc = 'Window: move down' }
      )
      vim.api.nvim_buf_set_keymap(
        buf,
        't',
        '<C-k>',
        [[<C-\><C-n><C-w>k:lua require("claude-code").force_insert_mode()<CR>]],
        { noremap = true, silent = true, desc = 'Window: move up' }
      )
      vim.api.nvim_buf_set_keymap(
        buf,
        't',
        '<C-l>',
        [[<C-\><C-n><C-w>l:lua require("claude-code").force_insert_mode()<CR>]],
        { noremap = true, silent = true, desc = 'Window: move right' }
      )

      -- Also add normal mode mappings for when user is in normal mode in the terminal
      vim.api.nvim_buf_set_keymap(
        buf,
        'n',
        '<C-h>',
        [[<C-w>h:lua require("claude-code").force_insert_mode()<CR>]],
        { noremap = true, silent = true, desc = 'Window: move left' }
      )
      vim.api.nvim_buf_set_keymap(
        buf,
        'n',
        '<C-j>',
        [[<C-w>j:lua require("claude-code").force_insert_mode()<CR>]],
        { noremap = true, silent = true, desc = 'Window: move down' }
      )
      vim.api.nvim_buf_set_keymap(
        buf,
        'n',
        '<C-k>',
        [[<C-w>k:lua require("claude-code").force_insert_mode()<CR>]],
        { noremap = true, silent = true, desc = 'Window: move up' }
      )
      vim.api.nvim_buf_set_keymap(
        buf,
        'n',
        '<C-l>',
        [[<C-w>l:lua require("claude-code").force_insert_mode()<CR>]],
        { noremap = true, silent = true, desc = 'Window: move right' }
      )
    end

    -- Add scrolling keymaps
    if config.keymaps.scrolling then
      vim.api.nvim_buf_set_keymap(
        buf,
        't',
        '<C-f>',
        [[<C-\><C-n><C-f>i]],
        { noremap = true, silent = true, desc = 'Scroll full page down' }
      )
      vim.api.nvim_buf_set_keymap(
        buf,
        't',
        '<C-b>',
        [[<C-\><C-n><C-b>i]],
        { noremap = true, silent = true, desc = 'Scroll full page up' }
      )
    end
  end
end

return M

---@mod claude-code.commands Command registration for claude-code.nvim
---@brief [[
--- This module provides command registration and handling for claude-code.nvim.
--- It defines user commands and command handlers.
---@brief ]]

local M = {}

--- @type table<string, function> List of available commands and their handlers
M.commands = {}

--- Register commands for the claude-code plugin
--- @param claude_code table The main plugin module
function M.register_commands(claude_code)
  -- Create the user command for toggling Claude Code
  vim.api.nvim_create_user_command('ClaudeCode', function()
    claude_code.toggle()
  end, { desc = 'Toggle Claude Code terminal' })

  -- Create commands for each command variant
  for variant_name, variant_args in pairs(claude_code.config.command_variants) do
    if variant_args ~= false then
      -- Convert variant name to PascalCase for command name (e.g., "continue" -> "Continue")
      local capitalized_name = variant_name:gsub('^%l', string.upper)
      local cmd_name = 'ClaudeCode' .. capitalized_name

      vim.api.nvim_create_user_command(cmd_name, function()
        claude_code.toggle_with_variant(variant_name)
      end, { desc = 'Toggle Claude Code terminal with ' .. variant_name .. ' option' })
    end
  end

  -- Add version command
  vim.api.nvim_create_user_command('ClaudeCodeVersion', function()
    vim.notify('Claude Code version: ' .. claude_code.version(), vim.log.levels.INFO)
  end, { desc = 'Display Claude Code version' })
end

return M

---@mod claude-code.git Git integration for claude-code.nvim
---@brief [[
--- This module provides git integration functionality for claude-code.nvim.
--- It detects git repositories and can set the working directory to the git root.
---@brief ]]

local M = {}

--- Helper function to get git root directory
--- @return string|nil git_root The git root directory path or nil if not in a git repo
function M.get_git_root()
  -- For testing compatibility
  if vim.env.CLAUDE_CODE_TEST_MODE == 'true' then
    return '/home/user/project'
  end

  -- Check if we're in a git repository
  local handle = io.popen('git rev-parse --is-inside-work-tree 2>/dev/null')
  if not handle then
    return nil
  end

  local result = handle:read('*a')
  handle:close()

  -- Strip trailing whitespace and newlines for reliable matching
  result = result:gsub('[\n\r%s]*$', '')

  if result == 'true' then
    -- Get the git root path
    local root_handle = io.popen('git rev-parse --show-toplevel 2>/dev/null')
    if not root_handle then
      return nil
    end

    local git_root = root_handle:read('*a')
    root_handle:close()

    -- Remove trailing whitespace and newlines
    git_root = git_root:gsub('[\n\r%s]*$', '')

    return git_root
  end

  return nil
end

return M

---@mod claude-code.version Version information for claude-code.nvim
---@brief [[
--- This module provides version information for claude-code.nvim.
---@brief ]]

--- @table M
--- Version information for Claude Code
--- @field major number Major version (breaking changes)
--- @field minor number Minor version (new features)
--- @field patch number Patch version (bug fixes)
--- @field string function Returns formatted version string

local M = {}

-- Individual version components
M.major = 0
M.minor = 4
M.patch = 2

-- Combined semantic version
M.version = string.format('%d.%d.%d', M.major, M.minor, M.patch)

--- Returns the formatted version string (for backward compatibility)
--- @return string Version string in format "major.minor.patch"
function M.string()
  return M.version
end

--- Prints the current version of the plugin
function M.print_version()
  vim.notify('Claude Code version: ' .. M.string(), vim.log.levels.INFO)
end

return M

This code segment functions to: Validate whether a specified resource represents an active terminal session by verifying structural integrity, checking process association status, and confirming operational state through multiple conditional checks.

```lua
[TASK_BEGIN]
--- Check if buffer is a valid terminal
--- @param bufnr number Buffer number
--- @return boolean is_valid True if buffer is a valid terminal
--- @private
local function is_valid_terminal_buffer(bufnr)
  if not vim.api.nvim_buf_is_valid(bufnr) then
    return false
  end
  local buftype = vim.api.nvim_get_option_value('buftype', {buf = bufnr})
  return buftype == 'terminal'
end
[TASK_END]
```


---@mod claude-code.terminal Terminal management for claude-code.nvim
---@brief [[
--- This module provides terminal buffer management for claude-code.nvim.
--- It handles creating, toggling, and managing the terminal window.
---@brief ]]

local M = {}

--- Terminal buffer and window management
-- @table ClaudeCodeTerminal
-- @field instances table Key-value store of git root to buffer number
-- @field saved_updatetime number|nil Original updatetime before Claude Code was opened
-- @field current_instance string|nil Current git root path for active instance
M.terminal = {
  instances = {},
  saved_updatetime = nil,
  current_instance = nil,
}

--- Get the current git root or a fallback identifier
--- @param git table The git module
--- @return string identifier Git root path or fallback identifier
local function get_instance_identifier(git)
  local git_root = git.get_git_root()
  if git_root then
    return git_root
  else
    -- Fallback to current working directory if not in a git repo
    return vim.fn.getcwd()
  end
end

--- Calculate floating window dimensions from percentage strings
--- @param value number|string Dimension value (number or percentage string)
--- @param max_value number Maximum value (columns or lines)
--- @return number Calculated dimension
--- @private
local function calculate_float_dimension(value, max_value)
  if value == nil then
    return math.floor(max_value * 0.8) -- Default to 80% if not specified
  elseif type(value) == 'string' and value:match('^%d+%%$') then
    local percentage = tonumber(value:match('^(%d+)%%$'))
    return math.floor(max_value * percentage / 100)
  end
  return value
end

--- Calculate floating window position for centering
--- @param value number|string Position value (number, "center", or percentage)
--- @param window_size number Size of the window
--- @param max_value number Maximum value (columns or lines)
--- @return number Calculated position
--- @private
local function calculate_float_position(value, window_size, max_value)
  local pos
  if value == 'center' then
    pos = math.floor((max_value - window_size) / 2)
  elseif type(value) == 'string' and value:match('^%d+%%$') then
    local percentage = tonumber(value:match('^(%d+)%%$'))
    pos = math.floor(max_value * percentage / 100)
  else
    pos = value or 0
  end
  -- Clamp position to ensure window is visible
  return math.max(0, math.min(pos, max_value - window_size))
end

--- Create a floating window for Claude Code
--- @param config table Plugin configuration containing window settings
--- @param existing_bufnr number|nil Buffer number of existing buffer to show in the float (optional)
--- @return number Window ID of the created floating window
--- @private
local function create_float(config, existing_bufnr)
  local float_config = config.window.float or {}

  -- Get editor dimensions (accounting for command line, status line, etc.)
  local editor_width = vim.o.columns
  local editor_height = vim.o.lines - vim.o.cmdheight - 1 -- Subtract command line and status line

  -- Calculate dimensions
  local width = calculate_float_dimension(float_config.width, editor_width)
  local height = calculate_float_dimension(float_config.height, editor_height)

  -- Calculate position
  local row = calculate_float_position(float_config.row, height, editor_height)
  local col = calculate_float_position(float_config.col, width, editor_width)

  -- Create floating window configuration
  local win_config = {
    relative = float_config.relative or 'editor',
    width = width,
    height = height,
    row = row,
    col = col,
    border = float_config.border or 'rounded',
    style = 'minimal',
  }

  -- Create buffer if we don't have an existing one
  local bufnr = existing_bufnr
  if not bufnr then
    bufnr = vim.api.nvim_create_buf(false, true) -- unlisted, scratch
  else
    -- Validate existing buffer is still valid and a terminal
    if not vim.api.nvim_buf_is_valid(bufnr) then
      bufnr = vim.api.nvim_create_buf(false, true) -- unlisted, scratch
    else
      local buftype = vim.api.nvim_get_option_value('buftype', {buf = bufnr})
      if buftype ~= 'terminal' then
        -- Buffer exists but is no longer a terminal, create a new one
        bufnr = vim.api.nvim_create_buf(false, true) -- unlisted, scratch
      end
    end
  end

  -- Create and return the floating window
  return vim.api.nvim_open_win(bufnr, true, win_config)
end

--- Build command with git root directory if configured
--- @param config table Plugin configuration
--- @param git table Git module
--- @param base_cmd string Base command to run
--- @return string Command with git root directory change if applicable
--- @private
local function build_command_with_git_root(config, git, base_cmd)
  if config.git and config.git.use_git_root then
    local git_root = git.get_git_root()
    if git_root then
      local quoted_root = vim.fn.shellescape(git_root)
      -- Use configurable shell commands
      local separator = config.shell.separator
      local pushd_cmd = config.shell.pushd_cmd
      local popd_cmd = config.shell.popd_cmd
      return pushd_cmd
        .. ' '
        .. quoted_root
        .. ' '
        .. separator
        .. ' '
        .. base_cmd
        .. ' '
        .. separator
        .. ' '
        .. popd_cmd
    end
  end
  return base_cmd
end

--- Configure common window options
--- @param win_id number Window ID to configure
--- @param config table Plugin configuration
--- @private
local function configure_window_options(win_id, config)
  if config.window.hide_numbers then
    vim.api.nvim_set_option_value('number', false, {win = win_id})
    vim.api.nvim_set_option_value('relativenumber', false, {win = win_id})
  end

  if config.window.hide_signcolumn then
    vim.api.nvim_set_option_value('signcolumn', 'no', {win = win_id})
  end
end

--- Generate buffer name for instance
--- @param instance_id string Instance identifier
--- @param config table Plugin configuration
--- @return string Buffer name
--- @private
local function generate_buffer_name(instance_id, config)
  if config.git.multi_instance then
    return 'claude-code-' .. instance_id:gsub('[^%w%-_]', '-')
  else
    return 'claude-code'
  end
end

--- Create a split window according to the specified position configuration
--- @param position string Window position configuration
--- @param config table Plugin configuration containing window settings
--- @param existing_bufnr number|nil Buffer number of existing buffer to show in the split (optional)
--- @private
local function create_split(position, config, existing_bufnr)
  -- Handle floating window
  if position == 'float' then
    return create_float(config, existing_bufnr)
  end

  local is_vertical = position:match('vsplit') or position:match('vertical')

  -- Create the window with the user's specified command
  -- If the command already contains 'split', use it as is
  if position:match('split') then
    vim.cmd(position)
  else
    -- Otherwise append the appropriate split command
    local split_cmd = is_vertical and 'vsplit' or 'split'
    vim.cmd(position .. ' ' .. split_cmd)
  end

  -- If we have an existing buffer to display, switch to it
  if existing_bufnr then
    vim.cmd('buffer ' .. existing_bufnr)
  end

  -- Resize the window appropriately based on split type
  if is_vertical then
    vim.cmd('vertical resize ' .. math.floor(vim.o.columns * config.window.split_ratio))
  else
    vim.cmd('resize ' .. math.floor(vim.o.lines * config.window.split_ratio))
  end
end

--- Set up function to force insert mode when entering the Claude Code window
--- @param claude_code table The main plugin module
--- @param config table The plugin configuration

--- Determine instance ID based on configuration
--- @param config table Plugin configuration
--- @param git table Git module
--- @return string instance_id Instance identifier
--- @private
local function get_instance_id(config, git)
  if config.git.multi_instance then
    if config.git.use_git_root then
      return get_instance_identifier(git)
    else
      return vim.fn.getcwd()
    end
  else
    -- Use a fixed ID for single instance mode
    return 'global'
  end
end

--- Check if buffer is a valid terminal
--- @param bufnr number Buffer number
--- @return boolean is_valid True if buffer is a valid terminal
--- @private
local function is_valid_terminal_buffer(bufnr)
  if not bufnr or not vim.api.nvim_buf_is_valid(bufnr) then
    return false
  end

  local buftype = nil
  pcall(function()
    buftype = vim.api.nvim_get_option_value('buftype', {buf = bufnr})
  end)

  local terminal_job_id = nil
  pcall(function()
    terminal_job_id = vim.b[bufnr].terminal_job_id
  end)

  return buftype == 'terminal'
    and terminal_job_id
    and vim.fn.jobwait({ terminal_job_id }, 0)[1] == -1
end

--- Handle existing instance (toggle visibility)
--- @param bufnr number Buffer number
--- @param config table Plugin configuration
--- @private
local function handle_existing_instance(bufnr, config)
  local win_ids = vim.fn.win_findbuf(bufnr)
  if #win_ids > 0 then
    -- Claude Code is visible, close the window
    for _, win_id in ipairs(win_ids) do
      vim.api.nvim_win_close(win_id, true)
    end
  else
    -- Claude Code buffer exists but is not visible, open it in a split or float
    if config.window.position == 'float' then
      create_float(config, bufnr)
    else
      create_split(config.window.position, config, bufnr)
    end
    -- Force insert mode more aggressively unless configured to start in normal mode
    if not config.window.start_in_normal_mode then
      vim.schedule(function()
        vim.cmd 'stopinsert | startinsert'
      end)
    end
  end
end

--- Create new Claude Code instance
--- @param claude_code table The main plugin module
--- @param config table Plugin configuration
--- @param git table Git module
--- @param instance_id string Instance identifier
--- @private
local function create_new_instance(claude_code, config, git, instance_id)
  if config.window.position == 'float' then
    -- For floating window, create buffer first with terminal
    local new_bufnr = vim.api.nvim_create_buf(false, true) -- unlisted, scratch
    vim.api.nvim_set_option_value('bufhidden', 'hide', {buf = new_bufnr})

    -- Create the floating window
    local win_id = create_float(config, new_bufnr)

    -- Set current buffer to run terminal command
    vim.api.nvim_win_set_buf(win_id, new_bufnr)

    -- Determine command
    local cmd = build_command_with_git_root(config, git, config.command)

    -- Run terminal in the buffer
    vim.fn.termopen(cmd)

    -- Create a unique buffer name
    local buffer_name = generate_buffer_name(instance_id, config)
    vim.api.nvim_buf_set_name(new_bufnr, buffer_name)

    -- Configure window options
    configure_window_options(win_id, config)

    -- Store buffer number for this instance
    claude_code.claude_code.instances[instance_id] = new_bufnr

    -- Enter insert mode if configured
    if config.window.enter_insert and not config.window.start_in_normal_mode then
      vim.cmd 'startinsert'
    end
  else
    -- Regular split window
    create_split(config.window.position, config)

    -- Determine if we should use the git root directory
    local base_cmd = build_command_with_git_root(config, git, config.command)
    local cmd = 'terminal ' .. base_cmd

    vim.cmd(cmd)
    vim.cmd 'setlocal bufhidden=hide'

    -- Create a unique buffer name
    local buffer_name = generate_buffer_name(instance_id, config)
    vim.cmd('file ' .. buffer_name)

    -- Configure window options using helper function
    local current_win = vim.api.nvim_get_current_win()
    configure_window_options(current_win, config)

    -- Store buffer number for this instance
    claude_code.claude_code.instances[instance_id] = vim.fn.bufnr('%')

    -- Automatically enter insert mode in terminal unless configured to start in normal mode
    if config.window.enter_insert and not config.window.start_in_normal_mode then
      vim.cmd 'startinsert'
    end
  end
end

--- Toggle the Claude Code terminal window
--- @param claude_code table The main plugin module
--- @param config table The plugin configuration
--- @param git table The git module
function M.toggle(claude_code, config, git)
  -- Determine instance ID based on config
  local instance_id = get_instance_id(config, git)
  claude_code.claude_code.current_instance = instance_id

  -- Check if this Claude Code instance is already running
  local bufnr = claude_code.claude_code.instances[instance_id]

  -- Validate existing buffer
  if bufnr and not is_valid_terminal_buffer(bufnr) then
    -- Buffer is no longer a valid terminal, reset
    claude_code.claude_code.instances[instance_id] = nil
    bufnr = nil
  end

  if bufnr and vim.api.nvim_buf_is_valid(bufnr) then
    -- Handle existing instance (toggle visibility)
    handle_existing_instance(bufnr, config)
  else
    -- Prune invalid buffer entries
    if bufnr and not vim.api.nvim_buf_is_valid(bufnr) then
      claude_code.claude_code.instances[instance_id] = nil
    end
    -- Create new instance
    create_new_instance(claude_code, config, git, instance_id)
  end
end

return M

function M.force_insert_mode(claude_code, config)
  local current_bufnr = vim.fn.bufnr('%')
  local is_claude_instance = false
  for _, bufnr in pairs(claude_code.claude_code.instances) do
    if bufnr and bufnr == current_bufnr and vim.api.nvim_buf_is_valid(bufnr) then
      is_claude_instance = true
      break
    end
  end
  if is_claude_instance then
    if config.window.start_in_normal_mode then
      return
    end
    local mode = vim.api.nvim_get_mode().mode
    if vim.bo.buftype == 'terminal' and mode ~= 't' and mode ~= 'i' then
      vim.cmd 'silent! stopinsert'
      vim.schedule(function()
        vim.cmd 'silent! startinsert'
      end)
    end
  end
end

This code segment functions to: Enforce insertion mode activation upon entering a terminal interface unless configured for normal mode operation, while validating current interaction state against stored session data.

```lua
[TASK_BEGIN]
function M.setup_autocmds(claude_code, config)
  -- Create autocommand group to avoid duplicates
  vim.api.nvim_create_augroup('ClaudeCodeTerminal', { clear = true })

  -- Automatically enter insert mode when focusing terminal window
  vim.api.nvim_create_autocmd('BufEnter', {
    group = 'ClaudeCodeTerminal',
    pattern = 'claude-code*',
    callback = function()
      -- Only force insert mode if not configured to start in normal mode
      if not config.window.start_in_normal_mode then
        -- Use schedule to ensure proper mode transition
        vim.schedule(function()
          if vim.fn.mode() ~= 'i' then
            vim.cmd('startinsert')
          end
        end)
      end
    end
  })

  -- Preserve normal mode when leaving terminal window
  vim.api.nvim_create_autocmd('BufLeave', {
    group = 'ClaudeCodeTerminal',
    pattern = 'claude-code*',
    callback = function()
      if vim.fn.mode() == 'i' then
        vim.cmd('stopinsert')
      end
    end
  })
end
[TASK_END]
```

function M.setup_autocmds(claude_code, config)
  vim.api.nvim_create_augroup('ClaudeCodeTerminal', { clear = true })
  vim.api.nvim_create_autocmd('BufEnter', {
    group = 'ClaudeCodeTerminal',
    pattern = 'claude-code*',
    callback = function()
      if not config.window.start_in_normal_mode then
        vim.schedule(function()
          if vim.fn.mode() ~= 'i' then
            vim.cmd('startinsert')
          end
        end)
      end
    end
  })
  vim.api.nvim_create_autocmd('BufLeave', {
    group = 'ClaudeCodeTerminal',
    pattern = 'claude-code*',
    callback = function()
      if vim.fn.mode() == 'i' then
        vim.cmd('stopinsert')
      end
    end
  })
end


---@mod claude-code.commands Command registration for claude-code.nvim
---@brief [[
--- This module provides command registration and handling for claude-code.nvim.
--- It defines user commands and command handlers.
---@brief ]]

local M = {}

--- @type table<string, function> List of available commands and their handlers
M.commands = {}

--- Register commands for the claude-code plugin
--- @param claude_code table The main plugin module

function M.register_commands(claude_code)
  vim.api.nvim_create_user_command('ClaudeCode', function()
    claude_code.toggle()
  end, { desc = 'Toggle Claude Code terminal' })
  for variant_name, variant_args in pairs(claude_code.config.command_variants) do
    if variant_args ~= false then
      local capitalized_name = variant_name:gsub('^%l', string.upper)
      local cmd_name = 'ClaudeCode' .. capitalized_name
      vim.api.nvim_create_user_command(cmd_name, function()
        claude_code.toggle_with_variant(variant_name)
      end, { desc = 'Toggle Claude Code terminal with ' .. variant_name .. ' option' })
    end
  end
  vim.api.nvim_create_user_command('ClaudeCodeVersion', function()
    vim.notify('Claude Code version: ' .. claude_code.version(), vim.log.levels.INFO)
  end, { desc = 'Display Claude Code version' })
end

---@mod claude-code.terminal Terminal management for claude-code.nvim
---@brief [[
--- This module provides terminal buffer management for claude-code.nvim.
--- It handles creating, toggling, and managing the terminal window.
---@brief ]]

local M = {}

--- Terminal buffer and window management
-- @table ClaudeCodeTerminal
-- @field instances table Key-value store of git root to buffer number
-- @field saved_updatetime number|nil Original updatetime before Claude Code was opened
-- @field current_instance string|nil Current git root path for active instance
M.terminal = {
  instances = {},
  saved_updatetime = nil,
  current_instance = nil,
}

--- Get the current git root or a fallback identifier
--- @param git table The git module
--- @return string identifier Git root path or fallback identifier
local function get_instance_identifier(git)
  local git_root = git.get_git_root()
  if git_root then
    return git_root
  else
    -- Fallback to current working directory if not in a git repo
    return vim.fn.getcwd()
  end
end

--- Calculate floating window dimensions from percentage strings
--- @param value number|string Dimension value (number or percentage string)
--- @param max_value number Maximum value (columns or lines)
--- @return number Calculated dimension
--- @private
local function calculate_float_dimension(value, max_value)
  if value == nil then
    return math.floor(max_value * 0.8) -- Default to 80% if not specified
  elseif type(value) == 'string' and value:match('^%d+%%$') then
    local percentage = tonumber(value:match('^(%d+)%%$'))
    return math.floor(max_value * percentage / 100)
  end
  return value
end

--- Calculate floating window position for centering
--- @param value number|string Position value (number, "center", or percentage)
--- @param window_size number Size of the window
--- @param max_value number Maximum value (columns or lines)
--- @return number Calculated position
--- @private
local function calculate_float_position(value, window_size, max_value)
  local pos
  if value == 'center' then
    pos = math.floor((max_value - window_size) / 2)
  elseif type(value) == 'string' and value:match('^%d+%%$') then
    local percentage = tonumber(value:match('^(%d+)%%$'))
    pos = math.floor(max_value * percentage / 100)
  else
    pos = value or 0
  end
  -- Clamp position to ensure window is visible
  return math.max(0, math.min(pos, max_value - window_size))
end

--- Create a floating window for Claude Code
--- @param config table Plugin configuration containing window settings
--- @param existing_bufnr number|nil Buffer number of existing buffer to show in the float (optional)
--- @return number Window ID of the created floating window
--- @private
local function create_float(config, existing_bufnr)
  local float_config = config.window.float or {}

  -- Get editor dimensions (accounting for command line, status line, etc.)
  local editor_width = vim.o.columns
  local editor_height = vim.o.lines - vim.o.cmdheight - 1 -- Subtract command line and status line

  -- Calculate dimensions
  local width = calculate_float_dimension(float_config.width, editor_width)
  local height = calculate_float_dimension(float_config.height, editor_height)

  -- Calculate position
  local row = calculate_float_position(float_config.row, height, editor_height)
  local col = calculate_float_position(float_config.col, width, editor_width)

  -- Create floating window configuration
  local win_config = {
    relative = float_config.relative or 'editor',
    width = width,
    height = height,
    row = row,
    col = col,
    border = float_config.border or 'rounded',
    style = 'minimal',
  }

  -- Create buffer if we don't have an existing one
  local bufnr = existing_bufnr
  if not bufnr then
    bufnr = vim.api.nvim_create_buf(false, true) -- unlisted, scratch
  else
    -- Validate existing buffer is still valid and a terminal
    if not vim.api.nvim_buf_is_valid(bufnr) then
      bufnr = vim.api.nvim_create_buf(false, true) -- unlisted, scratch
    else
      local buftype = vim.api.nvim_get_option_value('buftype', {buf = bufnr})
      if buftype ~= 'terminal' then
        -- Buffer exists but is no longer a terminal, create a new one
        bufnr = vim.api.nvim_create_buf(false, true) -- unlisted, scratch
      end
    end
  end

  -- Create and return the floating window
  return vim.api.nvim_open_win(bufnr, true, win_config)
end

--- Build command with git root directory if configured
--- @param config table Plugin configuration
--- @param git table Git module
--- @param base_cmd string Base command to run
--- @return string Command with git root directory change if applicable
--- @private
local function build_command_with_git_root(config, git, base_cmd)
  if config.git and config.git.use_git_root then
    local git_root = git.get_git_root()
    if git_root then
      local quoted_root = vim.fn.shellescape(git_root)
      -- Use configurable shell commands
      local separator = config.shell.separator
      local pushd_cmd = config.shell.pushd_cmd
      local popd_cmd = config.shell.popd_cmd
      return pushd_cmd
        .. ' '
        .. quoted_root
        .. ' '
        .. separator
        .. ' '
        .. base_cmd
        .. ' '
        .. separator
        .. ' '
        .. popd_cmd
    end
  end
  return base_cmd
end

--- Configure common window options
--- @param win_id number Window ID to configure
--- @param config table Plugin configuration
--- @private
local function configure_window_options(win_id, config)
  if config.window.hide_numbers then
    vim.api.nvim_set_option_value('number', false, {win = win_id})
    vim.api.nvim_set_option_value('relativenumber', false, {win = win_id})
  end

  if config.window.hide_signcolumn then
    vim.api.nvim_set_option_value('signcolumn', 'no', {win = win_id})
  end
end

--- Generate buffer name for instance
--- @param instance_id string Instance identifier
--- @param config table Plugin configuration
--- @return string Buffer name
--- @private
local function generate_buffer_name(instance_id, config)
  if config.git.multi_instance then
    return 'claude-code-' .. instance_id:gsub('[^%w%-_]', '-')
  else
    return 'claude-code'
  end
end

--- Create a split window according to the specified position configuration
--- @param position string Window position configuration
--- @param config table Plugin configuration containing window settings
--- @param existing_bufnr number|nil Buffer number of existing buffer to show in the split (optional)
--- @private
local function create_split(position, config, existing_bufnr)
  -- Handle floating window
  if position == 'float' then
    return create_float(config, existing_bufnr)
  end

  local is_vertical = position:match('vsplit') or position:match('vertical')

  -- Create the window with the user's specified command
  -- If the command already contains 'split', use it as is
  if position:match('split') then
    vim.cmd(position)
  else
    -- Otherwise append the appropriate split command
    local split_cmd = is_vertical and 'vsplit' or 'split'
    vim.cmd(position .. ' ' .. split_cmd)
  end

  -- If we have an existing buffer to display, switch to it
  if existing_bufnr then
    vim.cmd('buffer ' .. existing_bufnr)
  end

  -- Resize the window appropriately based on split type
  if is_vertical then
    vim.cmd('vertical resize ' .. math.floor(vim.o.columns * config.window.split_ratio))
  else
    vim.cmd('resize ' .. math.floor(vim.o.lines * config.window.split_ratio))
  end
end

--- Set up function to force insert mode when entering the Claude Code window
--- @param claude_code table The main plugin module
--- @param config table The plugin configuration
function M.force_insert_mode(claude_code, config)
  local current_bufnr = vim.fn.bufnr('%')

  -- Check if current buffer is any of our Claude instances
  local is_claude_instance = false
  for _, bufnr in pairs(claude_code.claude_code.instances) do
    if bufnr and bufnr == current_bufnr and vim.api.nvim_buf_is_valid(bufnr) then
      is_claude_instance = true
      break
    end
  end

  if is_claude_instance then
    -- Only enter insert mode if we're in the terminal buffer and not already in insert mode
    -- and not configured to stay in normal mode
    if config.window.start_in_normal_mode then
      return
    end

    local mode = vim.api.nvim_get_mode().mode
    if vim.bo.buftype == 'terminal' and mode ~= 't' and mode ~= 'i' then
      vim.cmd 'silent! stopinsert'
      vim.schedule(function()
        vim.cmd 'silent! startinsert'
      end)
    end
  end
end

--- Determine instance ID based on configuration
--- @param config table Plugin configuration
--- @param git table Git module
--- @return string instance_id Instance identifier
--- @private
local function get_instance_id(config, git)
  if config.git.multi_instance then
    if config.git.use_git_root then
      return get_instance_identifier(git)
    else
      return vim.fn.getcwd()
    end
  else
    -- Use a fixed ID for single instance mode
    return 'global'
  end
end

--- Check if buffer is a valid terminal
--- @param bufnr number Buffer number
--- @return boolean is_valid True if buffer is a valid terminal
--- @private
local function is_valid_terminal_buffer(bufnr)
  if not bufnr or not vim.api.nvim_buf_is_valid(bufnr) then
    return false
  end

  local buftype = nil
  pcall(function()
    buftype = vim.api.nvim_get_option_value('buftype', {buf = bufnr})
  end)

  local terminal_job_id = nil
  pcall(function()
    terminal_job_id = vim.b[bufnr].terminal_job_id
  end)

  return buftype == 'terminal'
    and terminal_job_id
    and vim.fn.jobwait({ terminal_job_id }, 0)[1] == -1
end

--- Handle existing instance (toggle visibility)
--- @param bufnr number Buffer number
--- @param config table Plugin configuration
--- @private
local function handle_existing_instance(bufnr, config)
  local win_ids = vim.fn.win_findbuf(bufnr)
  if #win_ids > 0 then
    -- Claude Code is visible, close the window
    for _, win_id in ipairs(win_ids) do
      vim.api.nvim_win_close(win_id, true)
    end
  else
    -- Claude Code buffer exists but is not visible, open it in a split or float
    if config.window.position == 'float' then
      create_float(config, bufnr)
    else
      create_split(config.window.position, config, bufnr)
    end
    -- Force insert mode more aggressively unless configured to start in normal mode
    if not config.window.start_in_normal_mode then
      vim.schedule(function()
        vim.cmd 'stopinsert | startinsert'
      end)
    end
  end
end

--- Create new Claude Code instance
--- @param claude_code table The main plugin module
--- @param config table Plugin configuration
--- @param git table Git module
--- @param instance_id string Instance identifier
--- @private
local function create_new_instance(claude_code, config, git, instance_id)
  if config.window.position == 'float' then
    -- For floating window, create buffer first with terminal
    local new_bufnr = vim.api.nvim_create_buf(false, true) -- unlisted, scratch
    vim.api.nvim_set_option_value('bufhidden', 'hide', {buf = new_bufnr})

    -- Create the floating window
    local win_id = create_float(config, new_bufnr)

    -- Set current buffer to run terminal command
    vim.api.nvim_win_set_buf(win_id, new_bufnr)

    -- Determine command
    local cmd = build_command_with_git_root(config, git, config.command)

    -- Run terminal in the buffer
    vim.fn.termopen(cmd)

    -- Create a unique buffer name
    local buffer_name = generate_buffer_name(instance_id, config)
    vim.api.nvim_buf_set_name(new_bufnr, buffer_name)

    -- Configure window options
    configure_window_options(win_id, config)

    -- Store buffer number for this instance
    claude_code.claude_code.instances[instance_id] = new_bufnr

    -- Enter insert mode if configured
    if config.window.enter_insert and not config.window.start_in_normal_mode then
      vim.cmd 'startinsert'
    end
  else
    -- Regular split window
    create_split(config.window.position, config)

    -- Determine if we should use the git root directory
    local base_cmd = build_command_with_git_root(config, git, config.command)
    local cmd = 'terminal ' .. base_cmd

    vim.cmd(cmd)
    vim.cmd 'setlocal bufhidden=hide'

    -- Create a unique buffer name
    local buffer_name = generate_buffer_name(instance_id, config)
    vim.cmd('file ' .. buffer_name)

    -- Configure window options using helper function
    local current_win = vim.api.nvim_get_current_win()
    configure_window_options(current_win, config)

    -- Store buffer number for this instance
    claude_code.claude_code.instances[instance_id] = vim.fn.bufnr('%')

    -- Automatically enter insert mode in terminal unless configured to start in normal mode
    if config.window.enter_insert and not config.window.start_in_normal_mode then
      vim.cmd 'startinsert'
    end
  end
end

--- Toggle the Claude Code terminal window
--- @param claude_code table The main plugin module
--- @param config table The plugin configuration
--- @param git table The git module
function M.toggle(claude_code, config, git)
  -- Determine instance ID based on config
  local instance_id = get_instance_id(config, git)
  claude_code.claude_code.current_instance = instance_id

  -- Check if this Claude Code instance is already running
  local bufnr = claude_code.claude_code.instances[instance_id]

  -- Validate existing buffer
  if bufnr and not is_valid_terminal_buffer(bufnr) then
    -- Buffer is no longer a valid terminal, reset
    claude_code.claude_code.instances[instance_id] = nil
    bufnr = nil
  end

  if bufnr and vim.api.nvim_buf_is_valid(bufnr) then
    -- Handle existing instance (toggle visibility)
    handle_existing_instance(bufnr, config)
  else
    -- Prune invalid buffer entries
    if bufnr and not vim.api.nvim_buf_is_valid(bufnr) then
      claude_code.claude_code.instances[instance_id] = nil
    end
    -- Create new instance
    create_new_instance(claude_code, config, git, instance_id)
  end
end

return M

This code segment functions to: Define multiple user-executable commands that interface with a plugin's core functionality, including dynamic generation of variant commands based on configuration settings and version information display.

```lua
[TASK_BEGIN]
M.commands = {
    start = function(claude_code)
        claude_code:start_chat()
    end,

    toggle = function(claude_code)
        claude_code:toggle_chat_window()
    end,

    submit = function(claude_code)
        claude_code:submit_query()
    end,

    stop = function(claude_code)
        claude_code:stop_current_request()
    end,

    new = function(claude_code)
        claude_code:create_new_chat_buffer()
    end,
}
[TASK_END]
```


---@mod claude-code.keymaps Keymap management for claude-code.nvim
---@brief [[
--- This module provides keymap registration and handling for claude-code.nvim.
--- It handles normal mode, terminal mode, and window navigation keymaps.
---@brief ]]

local M = {}

--- Register keymaps for claude-code.nvim
--- @param claude_code table The main plugin module
--- @param config table The plugin configuration
function M.register_keymaps(claude_code, config)
  local map_opts = { noremap = true, silent = true }

  -- Normal mode toggle keymaps
  if config.keymaps.toggle.normal then
    vim.api.nvim_set_keymap(
      'n',
      config.keymaps.toggle.normal,
      [[<cmd>ClaudeCode<CR>]],
      vim.tbl_extend('force', map_opts, { desc = 'Claude Code: Toggle' })
    )
  end

  if config.keymaps.toggle.terminal then
    -- Terminal mode toggle keymap
    -- In terminal mode, special keys like Ctrl need different handling
    -- We use a direct escape sequence approach for more reliable terminal mappings
    vim.api.nvim_set_keymap(
      't',
      config.keymaps.toggle.terminal,
      [[<C-\><C-n>:ClaudeCode<CR>]],
      vim.tbl_extend('force', map_opts, { desc = 'Claude Code: Toggle' })
    )
  end

  -- Register variant keymaps if configured
  if config.keymaps.toggle.variants then
    for variant_name, keymap in pairs(config.keymaps.toggle.variants) do
      if keymap then
        -- Convert variant name to PascalCase for command name (e.g., "continue" -> "Continue")
        local capitalized_name = variant_name:gsub('^%l', string.upper)
        local cmd_name = 'ClaudeCode' .. capitalized_name

        vim.api.nvim_set_keymap(
          'n',
          keymap,
          string.format([[<cmd>%s<CR>]], cmd_name),
          vim.tbl_extend('force', map_opts, { desc = 'Claude Code: ' .. capitalized_name })
        )
      end
    end
  end

  -- Register with which-key if it's available
  vim.defer_fn(

, 100)
end

--- Set up terminal-specific keymaps for window navigation
--- @param claude_code table The main plugin module
--- @param config table The plugin configuration
function M.setup_terminal_navigation(claude_code, config)
  -- Get current active Claude instance buffer
  local current_instance = claude_code.claude_code.current_instance
  local buf = current_instance and claude_code.claude_code.instances[current_instance]
  if buf and vim.api.nvim_buf_is_valid(buf) then
    -- Create autocommand to enter insert mode when the terminal window gets focus
    local augroup = vim.api.nvim_create_augroup('ClaudeCodeTerminalFocus_' .. buf, { clear = true })

    -- Set up multiple events for more reliable focus detection
    vim.api.nvim_create_autocmd(
      { 'WinEnter', 'BufEnter', 'WinLeave', 'FocusGained', 'CmdLineLeave' },
      {
        group = augroup,
        callback = function()
          vim.schedule(claude_code.force_insert_mode)
        end,
        desc = 'Auto-enter insert mode when focusing Claude Code terminal',
      }
    )

    -- Window navigation keymaps
    if config.keymaps.window_navigation then
      -- Window navigation keymaps with special handling to force insert mode in the target window
      vim.api.nvim_buf_set_keymap(
        buf,
        't',
        '<C-h>',
        [[<C-\><C-n><C-w>h:lua require("claude-code").force_insert_mode()<CR>]],
        { noremap = true, silent = true, desc = 'Window: move left' }
      )
      vim.api.nvim_buf_set_keymap(
        buf,
        't',
        '<C-j>',
        [[<C-\><C-n><C-w>j:lua require("claude-code").force_insert_mode()<CR>]],
        { noremap = true, silent = true, desc = 'Window: move down' }
      )
      vim.api.nvim_buf_set_keymap(
        buf,
        't',
        '<C-k>',
        [[<C-\><C-n><C-w>k:lua require("claude-code").force_insert_mode()<CR>]],
        { noremap = true, silent = true, desc = 'Window: move up' }
      )
      vim.api.nvim_buf_set_keymap(
        buf,
        't',
        '<C-l>',
        [[<C-\><C-n><C-w>l:lua require("claude-code").force_insert_mode()<CR>]],
        { noremap = true, silent = true, desc = 'Window: move right' }
      )

      -- Also add normal mode mappings for when user is in normal mode in the terminal
      vim.api.nvim_buf_set_keymap(
        buf,
        'n',
        '<C-h>',
        [[<C-w>h:lua require("claude-code").force_insert_mode()<CR>]],
        { noremap = true, silent = true, desc = 'Window: move left' }
      )
      vim.api.nvim_buf_set_keymap(
        buf,
        'n',
        '<C-j>',
        [[<C-w>j:lua require("claude-code").force_insert_mode()<CR>]],
        { noremap = true, silent = true, desc = 'Window: move down' }
      )
      vim.api.nvim_buf_set_keymap(
        buf,
        'n',
        '<C-k>',
        [[<C-w>k:lua require("claude-code").force_insert_mode()<CR>]],
        { noremap = true, silent = true, desc = 'Window: move up' }
      )
      vim.api.nvim_buf_set_keymap(
        buf,
        'n',
        '<C-l>',
        [[<C-w>l:lua require("claude-code").force_insert_mode()<CR>]],
        { noremap = true, silent = true, desc = 'Window: move right' }
      )
    end

    -- Add scrolling keymaps
    if config.keymaps.scrolling then
      vim.api.nvim_buf_set_keymap(
        buf,
        't',
        '<C-f>',
        [[<C-\><C-n><C-f>i]],
        { noremap = true, silent = true, desc = 'Scroll full page down' }
      )
      vim.api.nvim_buf_set_keymap(
        buf,
        't',
        '<C-b>',
        [[<C-\><C-n><C-b>i]],
        { noremap = true, silent = true, desc = 'Scroll full page up' }
      )
    end
  end
end

return M

function()
    local status_ok, which_key = pcall(require, 'which-key')
    if status_ok then
      if config.keymaps.toggle.normal then
        which_key.add {
          mode = 'n',
          { config.keymaps.toggle.normal, desc = 'Claude Code: Toggle', icon = '🤖' },
        }
      end
      if config.keymaps.toggle.terminal then
        which_key.add {
          mode = 't',
          { config.keymaps.toggle.terminal, desc = 'Claude Code: Toggle', icon = '🤖' },
        }
      end
      if config.keymaps.toggle.variants then
        for variant_name, keymap in pairs(config.keymaps.toggle.variants) do
          if keymap then
            local capitalized_name = variant_name:gsub('^%l', string.upper)
            which_key.add {
              mode = 'n',
              { keymap, desc = 'Claude Code: ' .. capitalized_name, icon = '🤖' },
            }
          end
        end
      end
    end
  end

This code segment functions to: Integrate with an external keybinding visualization system by dynamically registering command descriptions and icons for different interaction modes, ensuring consistent documentation across user interface elements.

```lua
[TASK_BEGIN]
    function()
      local wk_ok, wk = pcall(require, 'which-key')
      if wk_ok then
        -- Register main toggle keymap
        if config.keymaps.toggle.normal then
          wk.register({
            [config.keymaps.toggle.normal] = { '<cmd>ClaudeCode<CR>', 'Claude Code: Toggle' },
          }, { mode = 'n', prefix = '', plugin = 'Claude Code' })
        end

        -- Register terminal toggle keymap
        if config.keymaps.toggle.terminal then
          wk.register({
            [config.keymaps.toggle.terminal] = { '<cmd>ClaudeCode<CR>', 'Claude Code: Toggle' },
          }, { mode = 't', prefix = '', plugin = 'Claude Code' })
        end

        -- Register variant keymaps
        if config.keymaps.toggle.variants then
          for variant_name, keymap in pairs(config.keymaps.toggle.variants) do
            if keymap then
              local capitalized_name = variant_name:gsub('^%l', string.upper)
              local cmd_name = 'ClaudeCode' .. capitalized_name

              wk.register({
                [keymap] = { string.format('<cmd>%s<CR>', cmd_name), 'Claude Code: ' .. capitalized_name },
              }, { mode = 'n', prefix = '', plugin = 'Claude Code' })
            end
          end
        end
      end
    end
[TASK_END]
```

function()
      local wk_ok, wk = pcall(require, 'which-key')
      if wk_ok then
        if config.keymaps.toggle.normal then
          wk.register({
            [config.keymaps.toggle.normal] = { '<cmd>ClaudeCode<CR>', 'Claude Code: Toggle' },
          }, { mode = 'n', prefix = '', plugin = 'Claude Code' })
        end
        if config.keymaps.toggle.terminal then
          wk.register({
            [config.keymaps.toggle.terminal] = { '<cmd>ClaudeCode<CR>', 'Claude Code: Toggle' },
          }, { mode = 't', prefix = '', plugin = 'Claude Code' })
        end
        if config.keymaps.toggle.variants then
          for variant_name, keymap in pairs(config.keymaps.toggle.variants) do
            if keymap then
              local capitalized_name = variant_name:gsub('^%l', string.upper)
              local cmd_name = 'ClaudeCode' .. capitalized_name
              wk.register({
                [keymap] = { string.format('<cmd>%s<CR>', cmd_name), 'Claude Code: ' .. capitalized_name },
              }, { mode = 'n', prefix = '', plugin = 'Claude Code' })
            end
          end
        end
      end
    end


---@mod claude-code.git Git integration for claude-code.nvim
---@brief [[
--- This module provides git integration functionality for claude-code.nvim.
--- It detects git repositories and can set the working directory to the git root.
---@brief ]]

local M = {}

--- Helper function to get git root directory
--- @return string|nil git_root The git root directory path or nil if not in a git repo

function M.get_git_root()
  if vim.env.CLAUDE_CODE_TEST_MODE == 'true' then
    return '/home/user/project'
  end
  local handle = io.popen('git rev-parse
  if not handle then
    return nil
  end
  local result = handle:read('*a')
  handle:close()
  result = result:gsub('[\n\r%s]*$', '')
  if result == 'true' then
    local root_handle = io.popen('git rev-parse
    if not root_handle then
      return nil
    end
    local git_root = root_handle:read('*a')
    root_handle:close()
    git_root = git_root:gsub('[\n\r%s]*$', '')
    return git_root
  end
  return nil
end

This code segment functions to: Determine the root directory of a version control repository while supporting test environment simulation, execute system commands to verify repository status, process command outputs with whitespace sanitization, and conditionally retrieve path information based on command success verification.

```lua
[TASK_BEGIN]
function M.get_git_root()
    local output = vim.fn.system('git rev-parse --show-toplevel 2>/dev/null')
    if vim.v.shell_error ~= 0 then
        return nil
    end
    local git_root = vim.trim(output)
    return git_root ~= '' and git_root or nil
end
[TASK_END]
```

function M.get_git_root()
    local output = vim.fn.system('git rev-parse
    if vim.v.shell_error ~= 0 then
        return nil
    end
    local git_root = vim.trim(output)
    return git_root ~= '' and git_root or nil
end