Feature: LSP Lifecycle Manager coverage
  Exercise every code path in cleveragents.lsp.lifecycle to reach full
  branch and line coverage for the LspLifecycleManager and _ManagedServer
  classes.

  Background:
    Given llcov a fresh LspLifecycleManager instance

  # ---------- start_server: brand-new server (happy path) ----------

  Scenario: Start a new LSP server successfully
    Given llcov a server config named "local/pyright" with command "pyright-langserver"
    When llcov I start the server for workspace "/tmp/ws"
    Then llcov the returned client should not be None
    And llcov the server "local/pyright" should be registered

  # ---------- start_server: reuse existing alive server ----------

  Scenario: Reuse an existing alive server increments ref count
    Given llcov a server config named "local/pyright" with command "pyright-langserver"
    And llcov the server is already started for workspace "/tmp/ws"
    When llcov I start the server again for workspace "/tmp/ws"
    Then llcov the returned client should be the same as the original
    And llcov the ref count for "local/pyright" should be 2

  # ---------- start_server: initialize failure rolls back ----------

  Scenario: Start server rolls back transport when initialize fails
    Given llcov a server config named "local/broken" with command "broken-server"
    And llcov the LspClient initialize will raise an error
    When llcov I attempt to start the server for workspace "/tmp/ws"
    Then llcov a start error should have been raised
    And llcov the transport should have been stopped after init failure

  # ---------- start_server: race condition (another thread wins) ----------

  Scenario: Start server detects a race condition and reuses the winner
    Given llcov a server config named "local/pyright" with command "pyright-langserver"
    And llcov another thread already registered "local/pyright" before Phase 3
    When llcov I start the server for workspace "/tmp/ws" with race
    Then llcov the returned client should be the race winner client
    And llcov the losing transport should have been stopped

  # ---------- stop_server: decrement but still > 0 ----------

  Scenario: Stop server decrements ref count but keeps server alive
    Given llcov a server config named "local/pyright" with command "pyright-langserver"
    And llcov the server is already started for workspace "/tmp/ws"
    And llcov the server is started again to bump ref count
    When llcov I stop the server "local/pyright"
    Then llcov the server "local/pyright" should still be registered
    And llcov the ref count for "local/pyright" should be 1

  # ---------- stop_server: last reference, full shutdown ----------

  Scenario: Stop server shuts down when last reference is released
    Given llcov a server config named "local/pyright" with command "pyright-langserver"
    And llcov the server is already started for workspace "/tmp/ws"
    When llcov I stop the server "local/pyright"
    Then llcov the server "local/pyright" should not be registered
    And llcov shutdown should have been called on the client

  # ---------- stop_server: server not found ----------

  Scenario: Stop server raises error for unknown server
    When llcov I attempt to stop server "local/unknown"
    Then llcov a LspServerNotFoundError should have been raised

  # ---------- _shutdown_server: graceful shutdown ----------

  Scenario: Shutdown server calls client shutdown and transport stop
    Given llcov a server config named "local/pyright" with command "pyright-langserver"
    And llcov the server is already started for workspace "/tmp/ws"
    When llcov I trigger shutdown on the managed server
    Then llcov the client shutdown method should have been called
    And llcov the transport stop method should have been called

  # ---------- _shutdown_server: shutdown exception is caught ----------

  Scenario: Shutdown server catches exception from client shutdown
    Given llcov a server config named "local/pyright" with command "pyright-langserver"
    And llcov the server is already started for workspace "/tmp/ws"
    And llcov the client shutdown will raise an exception
    When llcov I trigger shutdown on the managed server
    Then llcov the transport stop method should have been called despite error

  # ---------- get_client: found ----------

  Scenario: Get client returns the client for a running server
    Given llcov a server config named "local/pyright" with command "pyright-langserver"
    And llcov the server is already started for workspace "/tmp/ws"
    When llcov I get the client for "local/pyright"
    Then llcov the returned client should not be None

  # ---------- get_client: not found ----------

  Scenario: Get client raises error for unknown server
    When llcov I attempt to get the client for "local/missing"
    Then llcov a LspServerNotFoundError should have been raised

  # ---------- health_check: server alive ----------

  Scenario: Health check returns true for alive server
    Given llcov a server config named "local/pyright" with command "pyright-langserver"
    And llcov the server is already started for workspace "/tmp/ws"
    When llcov I health check "local/pyright"
    Then llcov the health check result should be true

  # ---------- health_check: server not registered ----------

  Scenario: Health check returns false for unknown server
    When llcov I health check "local/nonexistent"
    Then llcov the health check result should be false

  # ---------- restart_server: happy path ----------

  Scenario: Restart server stops old transport and creates new client
    Given llcov a server config named "local/pyright" with command "pyright-langserver"
    And llcov the server is already started for workspace "/tmp/ws"
    When llcov I restart the server "local/pyright"
    Then llcov the returned client should not be None
    And llcov the old transport should have been stopped
    And llcov the managed server should have the new transport and client

  # ---------- restart_server: not found ----------

  Scenario: Restart server raises error for unknown server
    When llcov I attempt to restart server "local/ghost"
    Then llcov a LspServerNotFoundError should have been raised

  # ---------- restart_server: init failure during restart ----------

  Scenario: Restart server stops new transport when re-initialize fails
    Given llcov a server config named "local/pyright" with command "pyright-langserver"
    And llcov the server is already started for workspace "/tmp/ws"
    And llcov the LspClient initialize will raise on restart
    When llcov I attempt to restart server "local/pyright"
    Then llcov a restart error should have been raised
    And llcov the new transport should have been stopped after restart failure

  # ---------- stop_all: multiple servers ----------

  Scenario: Stop all shuts down every registered server
    Given llcov a server config named "local/pyright" with command "pyright-langserver"
    And llcov the server is already started for workspace "/tmp/ws"
    Given llcov a server config named "local/ruff" with command "ruff-langserver"
    And llcov the second server is already started for workspace "/tmp/ws2"
    When llcov I stop all servers
    Then llcov no servers should be registered

  # ---------- stop_all: with shutdown error ----------

  Scenario: Stop all handles shutdown errors gracefully
    Given llcov a server config named "local/pyright" with command "pyright-langserver"
    And llcov the server is already started for workspace "/tmp/ws"
    And llcov the client shutdown will raise an exception
    When llcov I stop all servers
    Then llcov no servers should be registered

  # ---------- stop_all: transport.stop() raises (covers lines 273-277) ----------

  Scenario: Stop all catches transport stop error in shutdown
    Given llcov a server config named "local/pyright" with command "pyright-langserver"
    And llcov the server is already started for workspace "/tmp/ws"
    And llcov the transport stop will raise an exception
    When llcov I stop all servers
    Then llcov no servers should be registered

  # ---------- list_running: with servers ----------

  Scenario: List running returns status for all servers
    Given llcov a server config named "local/pyright" with command "pyright-langserver"
    And llcov the server is already started for workspace "/tmp/ws"
    When llcov I list running servers
    Then llcov the running list should contain 1 entry
    And llcov the running entry for "local/pyright" should have workspace "/tmp/ws"
    And llcov the running entry for "local/pyright" should have alive status true
    And llcov the running entry for "local/pyright" should have ref count 1

  # ---------- list_running: empty ----------

  Scenario: List running returns empty list when no servers
    When llcov I list running servers
    Then llcov the running list should contain 0 entries

  # ---------- restart_server: 3-phase lock — health_check not blocked ----------

  Scenario: health_check is not blocked while restart_server is in progress
    Given llcov a server config named "local/pyright" with command "pyright-langserver"
    And llcov the server is already started for workspace "/tmp/ws"
    When llcov I restart the server concurrently and health_check runs during blocking IO
    Then llcov health_check should have completed without blocking
    And llcov the restarted client should not be None

  # ---------- restart_server: 3-phase lock — lock not held during initialize ----------

  Scenario: restart_server does not hold the lock during client.initialize
    Given llcov a server config named "local/pyright" with command "pyright-langserver"
    And llcov the server is already started for workspace "/tmp/ws"
    When llcov I restart the server and verify lock is free during initialize
    Then llcov the lock should have been acquirable during initialize
    And llcov the restarted client should not be None

  # ---------- restart_server: ref_count preserved after restart ----------

  Scenario: restart_server preserves the ref_count of the managed server
    Given llcov a server config named "local/pyright" with command "pyright-langserver"
    And llcov the server is already started for workspace "/tmp/ws"
    And llcov the server is started again to bump ref count
    When llcov I restart the server "local/pyright"
    Then llcov the ref count for "local/pyright" should be 2
