Feature: REPL module full coverage
  Comprehensive unit-level tests that exercise every function and branch
  inside ``src/cleveragents/cli/commands/repl.py`` to bring its coverage
  from 0% to near-100%.

  # -----------------------------------------------------------------
  # Constants
  # -----------------------------------------------------------------

  Scenario: Module constants are defined correctly
    Given replcov the repl module is imported
    Then the constant _MULTILINE_CONTINUATION equals backslash
    And the constant _LAST_COMMAND_TOKEN equals "!!"
    And the _REPL_COMMANDS list is non-empty
    And the _BUILTIN_COMMANDS list contains help exit and quit

  # -----------------------------------------------------------------
  # _get_prompt_context
  # -----------------------------------------------------------------

  Scenario: _get_prompt_context returns bare prompt when no env vars set
    Given neither CLEVERAGENTS_PROJECT nor CLEVERAGENTS_PLAN is set
    When I call _get_prompt_context
    Then the prompt result is "agents> "

  Scenario: _get_prompt_context returns project-only prompt
    Given CLEVERAGENTS_PROJECT is set to "proj1" and CLEVERAGENTS_PLAN is unset
    When I call _get_prompt_context
    Then the prompt result is "agents (proj1)> "

  Scenario: _get_prompt_context returns project+plan prompt
    Given CLEVERAGENTS_PROJECT is set to "proj1" and CLEVERAGENTS_PLAN is set to "plan1"
    When I call _get_prompt_context
    Then the prompt result is "agents (proj1/plan1)> "

  # -----------------------------------------------------------------
  # _setup_history
  # -----------------------------------------------------------------

  Scenario: _setup_history loads existing history file
    Given a temporary history file that exists
    When I call _setup_history with that path
    Then readline.read_history_file is called with the path
    And readline.set_history_length is called with 10000

  Scenario: _setup_history skips read when file does not exist
    Given a temporary history path that does not exist
    When I call _setup_history with that path
    Then readline.read_history_file is not called
    And readline.set_history_length is called with 10000

  # -----------------------------------------------------------------
  # _save_history
  # -----------------------------------------------------------------

  Scenario: _save_history writes the history file
    Given a temporary history path for saving
    When I call _save_history with that path
    Then readline.write_history_file is called with the path

  Scenario: _save_history suppresses OSError
    Given readline.write_history_file will raise OSError
    When I call _save_history with a dummy path
    Then no exception is raised

  # -----------------------------------------------------------------
  # _setup_completer
  # -----------------------------------------------------------------

  Scenario: _setup_completer installs readline completer
    When I call _setup_completer
    Then readline.set_completer is called with a callable
    And readline.set_completer_delims is called
    And readline.parse_and_bind is called with tab complete

  Scenario: The installed completer returns matching tokens
    Given the completer is installed via _setup_completer
    When I query the completer with text "ver" and state 0
    Then the completer returns "version"

  Scenario: The installed completer returns None for out-of-range state
    Given the completer is installed via _setup_completer
    When I query the completer with text "ver" and state 99
    Then the completer returns None

  # -----------------------------------------------------------------
  # _read_multiline
  # -----------------------------------------------------------------

  Scenario: _read_multiline reads continuation lines ending normally
    Given builtins.input will return "continued line"
    When I call _read_multiline with initial line ending in backslash
    Then the multiline result is "first continued line"

  Scenario: _read_multiline handles chained continuations
    Given builtins.input will return chained continuations
    When I call _read_multiline with initial line ending in backslash
    Then the multiline result is "first second third"

  Scenario: _read_multiline handles EOFError in continuation
    Given builtins.input will raise EOFError on continuation
    When I call _read_multiline with initial line ending in backslash
    Then the multiline result is "only"

  Scenario: _read_multiline handles KeyboardInterrupt in continuation
    Given builtins.input will raise KeyboardInterrupt on continuation
    When I call _read_multiline with initial line ending in backslash
    Then the multiline result is "only"

  # -----------------------------------------------------------------
  # _print_repl_help
  # -----------------------------------------------------------------

  Scenario: _print_repl_help runs without error
    When I call _print_repl_help
    Then no exception is raised from _print_repl_help

  # -----------------------------------------------------------------
  # dispatch_command
  # -----------------------------------------------------------------

  Scenario: dispatch_command returns 0 on success
    Given the root Typer app is mocked to succeed
    When I call dispatch_command with args "version"
    Then the dispatch return code is 0

  Scenario: dispatch_command returns code from SystemExit
    Given the root Typer app raises SystemExit with code 42
    When I call dispatch_command with args "bad"
    Then the dispatch return code is 42

  Scenario: dispatch_command returns 0 for SystemExit with None code
    Given the root Typer app raises SystemExit with None code
    When I call dispatch_command with args "something"
    Then the dispatch return code is 0

  Scenario: dispatch_command returns 130 on KeyboardInterrupt
    Given the root Typer app raises KeyboardInterrupt
    When I call dispatch_command with args "slow"
    Then the dispatch return code is 130

  Scenario: dispatch_command returns 1 and prints error on generic Exception
    Given the root Typer app raises RuntimeError boom
    When I call dispatch_command with args "fail"
    Then the dispatch return code is 1
    And the dispatch error output contains "boom"

  # -----------------------------------------------------------------
  # run_repl — exit commands
  # -----------------------------------------------------------------

  Scenario: run_repl exits on :exit command
    Given the REPL input sequence is
      | line  |
      | :exit |
    When I run the REPL loop
    Then the REPL loop exit code is 0

  Scenario: run_repl exits on :quit command
    Given the REPL input sequence is
      | line  |
      | :quit |
    When I run the REPL loop
    Then the REPL loop exit code is 0

  # -----------------------------------------------------------------
  # run_repl — help
  # -----------------------------------------------------------------

  Scenario: run_repl handles :help and continues
    Given the REPL input sequence is
      | line  |
      | :help |
      | :exit |
    When I run the REPL loop
    Then the REPL loop output contains "Built-in commands"

  # -----------------------------------------------------------------
  # run_repl — !! repeat
  # -----------------------------------------------------------------

  Scenario: run_repl repeats last command with !!
    Given the REPL input sequence is
      | line    |
      | version |
      | !!      |
      | :exit   |
    And dispatch_command is mocked for run_repl
    When I run the REPL loop
    Then dispatch_command was called at least 2 times with "version"

  Scenario: run_repl warns when !! has no previous command
    Given the REPL input sequence is
      | line  |
      | !!    |
      | :exit |
    When I run the REPL loop
    Then the REPL loop output contains "No previous command"

  # -----------------------------------------------------------------
  # run_repl — empty and whitespace input
  # -----------------------------------------------------------------

  Scenario: run_repl skips empty lines
    Given the REPL input sequence is
      | line  |
      |       |
      | :exit |
    When I run the REPL loop
    Then the REPL loop exit code is 0

  # -----------------------------------------------------------------
  # run_repl — multiline
  # -----------------------------------------------------------------

  Scenario: run_repl handles multiline input ending with backslash
    Given the REPL input sequence for multiline is "version \" then "" then ":exit"
    And dispatch_command is mocked for run_repl
    When I run the REPL loop
    Then the REPL loop exit code is 0

  # -----------------------------------------------------------------
  # run_repl — KeyboardInterrupt / EOFError
  # -----------------------------------------------------------------

  Scenario: run_repl handles KeyboardInterrupt and continues
    Given the REPL input sequence for interrupt then exit
    When I run the REPL loop
    Then the REPL loop exit code is 0

  Scenario: run_repl handles EOFError for clean exit
    Given the REPL input sequence is empty
    When I run the REPL loop
    Then the REPL loop exit code is 0

  # -----------------------------------------------------------------
  # run_repl — shlex parse error
  # -----------------------------------------------------------------

  Scenario: run_repl handles shlex parse error gracefully
    Given the REPL input sequence has an unclosed quote then exit
    When I run the REPL loop
    Then the REPL loop output contains "Parse error"

  # -----------------------------------------------------------------
  # run_repl — history on/off
  # -----------------------------------------------------------------

  Scenario: run_repl with history enabled calls setup and save
    Given the REPL input sequence is
      | line  |
      | :exit |
    When I run the REPL loop with history enabled
    Then _setup_history was called
    And _save_history was called

  Scenario: run_repl with no_history skips setup and save
    Given the REPL input sequence is
      | line  |
      | :exit |
    When I run the REPL loop with no_history
    Then _setup_history was not called
    And _save_history was not called

  # -----------------------------------------------------------------
  # run_repl — dispatch a real-ish command
  # -----------------------------------------------------------------

  Scenario: run_repl dispatches tokenised command
    Given the REPL input sequence is
      | line  |
      | info  |
      | :exit |
    And dispatch_command is mocked for run_repl
    When I run the REPL loop
    Then dispatch_command was called with args "info"

  # -----------------------------------------------------------------
  # repl_callback
  # -----------------------------------------------------------------

  Scenario: repl_callback starts REPL when stdout is a tty
    Given sys.stdout.isatty returns True
    And run_repl is mocked to return 0
    When I call repl_callback
    Then run_repl was invoked
    And repl_callback raises SystemExit with code 0

  Scenario: repl_callback starts REPL when CLEVERAGENTS_FORCE_REPL is set
    Given sys.stdout.isatty returns False
    And CLEVERAGENTS_FORCE_REPL is set in the environment
    And run_repl is mocked to return 0
    When I call repl_callback
    Then run_repl was invoked

  Scenario: repl_callback does nothing when not a tty and no force env
    Given sys.stdout.isatty returns False
    And CLEVERAGENTS_FORCE_REPL is not set
    When I call repl_callback
    Then run_repl was not invoked

  # -----------------------------------------------------------------
  # Completer edge case: empty text matches all tokens
  # -----------------------------------------------------------------

  Scenario: The installed completer returns first token for empty text
    Given the completer is installed via _setup_completer
    When I query the completer with empty text and state 0
    Then the completer returns a non-None value
