"""
API Hook Client - Python client for the Hook API.

This module provides a Python client for interacting with the Hook API exposed by the application on port 8999.
 It is used for:
 - Automated GUI testing via the `live_gui` pytest fixture
 - External tool integration
 - Remote control of the application

Architecture:
 - Uses requests library for HTTP communication
 - All methods return dict[str, Any] or None
 - Handles connection errors gracefully (returns None on failure)

Key Method Categories:
 1. Connection: wait_for_server, get_status
 2. State Query: get_project, get_session. get_performance, get_mma_status
 3. GUI Manipulation: click, set_value, select_tab, select_list_item
 4. Polling: wait_for_event
 5. HITL: request_confirmation

Timeout Handling:
 - Standard operations: 5s timeout
 - HITL dialogs: 60s timeout (waits for human input)

Integration:
 - Used by simulation tests (tests/visual_sim_mma_v2.py)
 - Used by external tools for automation

See Also:
 - src/api_hooks.py for the server implementation
 - docs/guide_tools.md for Hook API documentation
"""
from __future__ import annotations

import requests # type: ignore[import-untyped]
import sys
import time

from typing import Any


class ApiHookClient:
 
 def __init__(self, base_url: str = "http://127.0.0.1:8999", api_key: str | None = None):
  """
    [C: src/mcp_client.py:_DDGParser.__init__, src/mcp_client.py:_TextExtractor.__init__]
  """
  self.base_url = base_url.rstrip('/')
  self.api_key  = api_key

 def _make_request(self, method: str, path: str, data: dict | None = None, timeout: float = 5.0) -> dict[str, Any] | None:
  """
  Helper to make HTTP requests to the hook server.
  [C: tests/test_api_hook_client.py:test_unsupported_method_error]
  """
  url = f"{self.base_url}{path}"
  headers = {}
  if self.api_key:
   headers["X-API-KEY"] = self.api_key
  if method not in ('GET', 'POST', 'DELETE'):
   raise ValueError(f"Unsupported HTTP method: {method}")
  try:
   if   method == 'GET':    response = requests.get(url, headers=headers, timeout=timeout)
   elif method == 'POST':   response = requests.post(url, json=data, headers=headers, timeout=timeout)
   elif method == 'DELETE': response = requests.delete(url, headers=headers, timeout=timeout)
   
   if response.status_code == 200:
    return response.json()
   else:
    sys.stderr.write(f"[DEBUG Client] Request failed: {method} {path} - status={response.status_code}\n")
    sys.stderr.flush()
   return None
  except Exception as e:
   sys.stderr.write(f"[DEBUG Client] Request error: {method} {path} - {e}\n")
   sys.stderr.flush()
   return None

 def wait_for_server(self, timeout: int = 15) -> bool:
  """
  Polls the health endpoint until the server responds or timeout occurs.
  [C: simulation/live_walkthrough.py:main, simulation/ping_pong.py:main, simulation/sim_base.py:BaseSimulation.setup, tests/smoke_status_hook.py:test_status_hook, tests/test_ai_settings_layout.py:test_change_provider_via_hook, tests/test_ai_settings_layout.py:test_set_params_via_custom_callback, tests/test_auto_switch_sim.py:test_auto_switch_sim, tests/test_conductor_api_hook_integration.py:test_conductor_integrates_api_hook_client_for_verification, tests/test_deepseek_infra.py:test_gui_provider_list_via_hooks, tests/test_extended_sims.py:test_ai_settings_sim_live, tests/test_extended_sims.py:test_context_sim_live, tests/test_extended_sims.py:test_execution_sim_live, tests/test_extended_sims.py:test_tools_sim_live, tests/test_external_editor_gui.py:test_button_click_is_received, tests/test_external_editor_gui.py:test_patch_modal_shows_with_configured_editor, tests/test_external_editor_gui.py:test_vscode_launches_with_diff_view, tests/test_gui2_parity.py:test_gui2_click_hook_works, tests/test_gui2_parity.py:test_gui2_custom_callback_hook_works, tests/test_gui2_parity.py:test_gui2_set_value_hook_works, tests/test_gui_context_presets.py:test_gui_context_preset_save_load, tests/test_hooks.py:test_live_hook_server_responses, tests/test_live_workflow.py:test_full_live_workflow, tests/test_mma_concurrent_tracks_sim.py:test_mma_concurrent_tracks_execution, tests/test_mma_concurrent_tracks_stress_sim.py:test_mma_concurrent_tracks_stress, tests/test_mma_step_mode_sim.py:test_mma_step_mode_approval_flow, tests/test_patch_modal_gui.py:test_patch_apply_modal_workflow, tests/test_patch_modal_gui.py:test_patch_modal_appears_on_trigger, tests/test_phase6_simulation.py:test_ast_inspector_modal_opens, tests/test_phase6_simulation.py:test_batch_operations_shift_click, tests/test_phase6_simulation.py:test_slice_editor_add_remove, tests/test_preset_windows_layout.py:test_api_hook_under_load, tests/test_preset_windows_layout.py:test_preset_windows_opening, tests/test_rag_phase4_final_verify.py:test_phase4_final_verify, tests/test_rag_phase4_stress.py:test_rag_large_codebase_verification_sim, tests/test_rag_visual_sim.py:test_rag_full_lifecycle_sim, tests/test_rag_visual_sim.py:test_rag_settings_persistence_sim, tests/test_selectable_ui.py:test_selectable_label_stability, tests/test_system_prompt_sim.py:test_system_prompt_sim, tests/test_tool_management_layout.py:test_tool_management_gettable_fields, tests/test_tool_management_layout.py:test_tool_management_state_updates, tests/test_ui_cache_controls_sim.py:test_ui_cache_controls, tests/test_undo_redo_sim.py:test_undo_redo_context_mutation, tests/test_undo_redo_sim.py:test_undo_redo_discussion_mutation, tests/test_undo_redo_sim.py:test_undo_redo_lifecycle, tests/test_visual_mma.py:test_visual_mma_components, tests/test_visual_orchestration.py:test_mma_epic_lifecycle, tests/test_visual_sim_gui_ux.py:test_gui_track_creation, tests/test_visual_sim_gui_ux.py:test_gui_ux_event_routing, tests/test_visual_sim_mma_v2.py:test_mma_complete_lifecycle, tests/test_workspace_profiles_sim.py:test_workspace_profiles_restoration, tests/test_z_negative_flows.py:test_mock_error_result, tests/test_z_negative_flows.py:test_mock_malformed_json, tests/test_z_negative_flows.py:test_mock_timeout]
  """
  start = time.time()
  while time.time() - start < timeout:
   status = self.get_status()
   if status and (status.get("status") == "ok" or "status" in status):
    return True
   time.sleep(0.5)
  return False

 def get_status(self) -> dict[str, Any]:
  """
  Checks the health of the hook server.
  [C: tests/test_api_hook_client.py:test_get_status_success, tests/test_headless_simulation.py:test_mma_track_lifecycle_simulation, tests/test_hooks.py:test_live_hook_server_responses, tests/test_mma_concurrent_tracks_stress_sim.py:test_mma_concurrent_tracks_stress, tests/test_phase6_simulation.py:test_ast_inspector_modal_opens, tests/test_phase6_simulation.py:test_batch_operations_shift_click, tests/test_phase6_simulation.py:test_slice_editor_add_remove, tests/test_preset_windows_layout.py:make_request, tests/test_preset_windows_layout.py:test_preset_windows_opening, tests/test_ui_cache_controls_sim.py:test_ui_cache_controls]
  """
  res = self._make_request('GET', '/status')
  if res is None:
  # For backward compatibility with tests expecting ConnectionError
  # But our _make_request handles it. Let's return empty if failed.
   return {}
  return res

 def post_session(self, session_entries: list[dict]) -> dict[str, Any]:
  """
  Updates the session history.
  [C: tests/test_gui_stress_performance.py:test_comms_volume_stress_performance, tests/test_live_workflow.py:test_full_live_workflow]
  """
  return self._make_request('POST', '/api/session', data={"session": {"entries": session_entries}}) or {}

 def get_events(self) -> list[dict[str, Any]]:
  """Retrieves any pending events from the API event queue."""
  res = self._make_request('GET', '/api/events')
  return res.get("events", []) if res else []

 def clear_events(self) -> list[dict[str, Any]]:
  """
  Retrieves and clears the event queue.
  [C: simulation/sim_base.py:BaseSimulation.setup]
  """
  return self.get_events()

 def wait_for_event(self, event_type: str, timeout: int = 5) -> dict[str, Any] | None:
  """
  [C: simulation/sim_base.py:BaseSimulation.wait_for_event, simulation/sim_execution.py:ExecutionSimulation.run, tests/test_z_negative_flows.py:test_mock_error_result, tests/test_z_negative_flows.py:test_mock_malformed_json, tests/test_z_negative_flows.py:test_mock_timeout]
  """
  start = time.time()
  while time.time() - start < timeout:
   events = self.get_events()
   for ev in events:
    if ev.get("type") == event_type or ev.get("event_type") == event_type:
     return ev
   time.sleep(0.2)
  return None

 def post_gui(self, payload: dict) -> dict[str, Any]:
  """
  Pushes an event to the GUI's AsyncEventQueue via the /api/gui endpoint.
  [C: tests/test_ai_settings_layout.py:test_set_params_via_custom_callback, tests/test_api_hook_client.py:test_post_gui_success, tests/test_gui2_parity.py:test_gui2_custom_callback_hook_works, tests/test_gui2_parity.py:test_gui2_set_value_hook_works]
  """
  return self._make_request('POST', '/api/gui', data=payload) or {}

 def push_event(self, action: str, payload: dict) -> dict[str, Any]:
  """
  Convenience to push a GUI task.
  [C: tests/test_auto_switch_sim.py:test_auto_switch_sim, tests/test_auto_switch_sim.py:trigger_tier, tests/test_external_editor_gui.py:test_button_click_is_received, tests/test_external_editor_gui.py:test_patch_modal_shows_with_configured_editor, tests/test_external_editor_gui.py:test_vscode_launches_with_diff_view, tests/test_gui_context_presets.py:test_gui_context_preset_save_load, tests/test_gui_text_viewer.py:test_text_viewer_state_update, tests/test_patch_modal_gui.py:test_patch_apply_modal_workflow, tests/test_patch_modal_gui.py:test_patch_modal_appears_on_trigger, tests/test_preset_windows_layout.py:test_preset_windows_opening, tests/test_saved_presets_sim.py:test_preset_manager_modal, tests/test_saved_presets_sim.py:test_preset_switching, tests/test_tool_management_layout.py:test_tool_management_state_updates, tests/test_tool_presets_sim.py:test_tool_preset_switching, tests/test_visual_mma.py:test_visual_mma_components, tests/test_visual_sim_gui_ux.py:test_gui_track_creation, tests/test_visual_sim_gui_ux.py:test_gui_ux_event_routing, tests/test_workspace_profiles_sim.py:test_workspace_profiles_restoration, tests/test_z_negative_flows.py:test_mock_error_result, tests/test_z_negative_flows.py:test_mock_malformed_json, tests/test_z_negative_flows.py:test_mock_timeout]
  """
  return self.post_gui({"action": action, **payload})

#region: Data

 def get_gui_state(self) -> dict[str, Any]:
  """
  Returns the full GUI state available via the hook API.
  [C: tests/test_ai_settings_layout.py:test_change_provider_via_hook, tests/test_ai_settings_layout.py:test_set_params_via_custom_callback, tests/test_conductor_api_hook_integration.py:simulate_conductor_phase_completion, tests/test_external_editor_gui.py:test_button_click_is_received, tests/test_external_editor_gui.py:test_patch_modal_shows_with_configured_editor, tests/test_external_editor_gui.py:test_vscode_launches_with_diff_view, tests/test_gui_text_viewer.py:test_text_viewer_state_update, tests/test_hooks.py:test_live_hook_server_responses, tests/test_live_gui_integration_v2.py:test_api_gui_state_live, tests/test_live_workflow.py:test_full_live_workflow, tests/test_live_workflow.py:wait_for_value, tests/test_patch_modal_gui.py:test_patch_apply_modal_workflow, tests/test_patch_modal_gui.py:test_patch_modal_appears_on_trigger, tests/test_rag_phase4_final_verify.py:test_phase4_final_verify, tests/test_rag_phase4_stress.py:test_rag_large_codebase_verification_sim, tests/test_saved_presets_sim.py:test_preset_manager_modal, tests/test_saved_presets_sim.py:test_preset_switching, tests/test_task_dag_popout_sim.py:test_task_dag_popout, tests/test_tool_management_layout.py:test_tool_management_gettable_fields, tests/test_tool_management_layout.py:test_tool_management_state_updates, tests/test_tool_presets_sim.py:test_tool_preset_switching, tests/test_usage_analytics_popout_sim.py:test_usage_analytics_popout, tests/test_visual_mma.py:test_visual_mma_components]
  """
  return self._make_request('GET', '/api/gui/state') or {}

 def get_value(self, item: str) -> Any:
  """
  Gets the value of a GUI item via its mapped field.
  [C: simulation/sim_ai_settings.py:AISettingsSimulation.run, simulation/sim_base.py:BaseSimulation.get_value, simulation/sim_base.py:BaseSimulation.setup, simulation/sim_base.py:BaseSimulation.wait_for_element, simulation/sim_context.py:ContextSimulation.run, simulation/sim_execution.py:ExecutionSimulation.run, simulation/workflow_sim.py:WorkflowSimulator.run_discussion_turn_async, simulation/workflow_sim.py:WorkflowSimulator.wait_for_ai_response, tests/smoke_status_hook.py:test_status_hook, tests/smoke_status_hook.py:wait_for_value, tests/test_auto_switch_sim.py:test_auto_switch_sim, tests/test_deepseek_infra.py:test_gui_provider_list_via_hooks, tests/test_extended_sims.py:test_ai_settings_sim_live, tests/test_gui2_parity.py:test_gui2_click_hook_works, tests/test_gui2_parity.py:test_gui2_set_value_hook_works, tests/test_rag_phase4_final_verify.py:test_phase4_final_verify, tests/test_rag_phase4_stress.py:test_rag_large_codebase_verification_sim, tests/test_rag_visual_sim.py:test_rag_full_lifecycle_sim, tests/test_rag_visual_sim.py:test_rag_settings_persistence_sim, tests/test_selectable_ui.py:test_selectable_label_stability, tests/test_system_prompt_sim.py:test_system_prompt_sim, tests/test_undo_redo_sim.py:test_undo_redo_context_mutation, tests/test_undo_redo_sim.py:test_undo_redo_discussion_mutation, tests/test_undo_redo_sim.py:test_undo_redo_lifecycle, tests/test_workspace_profiles_sim.py:test_workspace_profiles_restoration]
  """
  # Dict-key bracket notation: e.g. 'show_windows["Project Settings"]'
  if "[" in item and item.endswith("]"):
   dict_name, _, key_part = item.partition("[")
   key = key_part[:-1].strip().strip("'\"")
   state = self.get_gui_state()
   if isinstance(state.get(dict_name), dict) and key in state[dict_name]:
    return state[dict_name][key]
   return None
  # Try state endpoint first (new preferred way)
  state = self.get_gui_state()
  if item in state:
   return state[item]
   # Fallback for thinking/live/prior which are in diagnostics
  diag = self.get_gui_diagnostics()
  if diag and item in diag:
   return diag[item]
   # Map common indicator tags to diagnostics keys
  mapping = {
   "thinking_indicator": "thinking",
   "operations_live_indicator": "live",
   "prior_session_indicator": "prior"
  }
  key = mapping.get(item)
  if diag and key and key in diag:
   return diag[key]
  return None

 def get_text_value(self, item_tag: str) -> str | None:
  """
  Wraps get_value and returns its string representation, or None.
  [C: tests/test_api_hook_client.py:test_get_text_value]
  """
  val = self.get_value(item_tag)
  return str(val) if val is not None else None

 def set_value(self, item: str, value: Any) -> dict[str, Any]:
  """
  Sets the value of a GUI widget.
  [C: simulation/live_walkthrough.py:main, simulation/ping_pong.py:main, simulation/sim_ai_settings.py:AISettingsSimulation.run, simulation/sim_base.py:BaseSimulation.setup, simulation/workflow_sim.py:WorkflowSimulator.create_discussion, simulation/workflow_sim.py:WorkflowSimulator.run_discussion_turn_async, simulation/workflow_sim.py:WorkflowSimulator.setup_new_project, simulation/workflow_sim.py:WorkflowSimulator.truncate_history, tests/smoke_status_hook.py:test_status_hook, tests/test_ai_settings_layout.py:test_change_provider_via_hook, tests/test_auto_switch_sim.py:test_auto_switch_sim, tests/test_deepseek_infra.py:test_gui_provider_list_via_hooks, tests/test_extended_sims.py:test_ai_settings_sim_live, tests/test_extended_sims.py:test_context_sim_live, tests/test_extended_sims.py:test_execution_sim_live, tests/test_extended_sims.py:test_tools_sim_live, tests/test_gui2_parity.py:test_gui2_click_hook_works, tests/test_gui2_performance.py:test_performance_benchmarking, tests/test_live_gui_integration_v2.py:test_api_gui_state_live, tests/test_live_workflow.py:test_full_live_workflow, tests/test_mma_concurrent_tracks_sim.py:test_mma_concurrent_tracks_execution, tests/test_mma_concurrent_tracks_stress_sim.py:test_mma_concurrent_tracks_stress, tests/test_mma_step_mode_sim.py:test_mma_step_mode_approval_flow, tests/test_rag_phase4_final_verify.py:test_phase4_final_verify, tests/test_rag_phase4_stress.py:test_rag_large_codebase_verification_sim, tests/test_rag_visual_sim.py:test_rag_full_lifecycle_sim, tests/test_rag_visual_sim.py:test_rag_settings_persistence_sim, tests/test_saved_presets_sim.py:test_preset_manager_modal, tests/test_selectable_ui.py:test_selectable_label_stability, tests/test_system_prompt_sim.py:test_system_prompt_sim, tests/test_task_dag_popout_sim.py:test_task_dag_popout, tests/test_tool_presets_sim.py:test_tool_preset_switching, tests/test_undo_redo_sim.py:test_undo_redo_context_mutation, tests/test_undo_redo_sim.py:test_undo_redo_discussion_mutation, tests/test_undo_redo_sim.py:test_undo_redo_lifecycle, tests/test_usage_analytics_popout_sim.py:test_usage_analytics_popout, tests/test_visual_mma.py:test_visual_mma_components, tests/test_visual_orchestration.py:test_mma_epic_lifecycle, tests/test_visual_sim_mma_v2.py:test_mma_complete_lifecycle, tests/test_workspace_profiles_sim.py:test_workspace_profiles_restoration, tests/test_z_negative_flows.py:test_mock_error_result, tests/test_z_negative_flows.py:test_mock_malformed_json, tests/test_z_negative_flows.py:test_mock_timeout]
  """
  return self.post_gui({"action": "set_value", "item": item, "value": value})

#endregion: Data

#region: Input

 def click(self, item: str, user_data: Any = None) -> dict[str, Any]:
  """
  Simulates a button click.
  [C: simulation/live_walkthrough.py:main, simulation/ping_pong.py:main, simulation/sim_base.py:BaseSimulation.setup, simulation/sim_context.py:ContextSimulation.run, simulation/sim_execution.py:ExecutionSimulation.run, simulation/workflow_sim.py:WorkflowSimulator.create_discussion, simulation/workflow_sim.py:WorkflowSimulator.load_prior_log, simulation/workflow_sim.py:WorkflowSimulator.run_discussion_turn_async, simulation/workflow_sim.py:WorkflowSimulator.setup_new_project, simulation/workflow_sim.py:WorkflowSimulator.truncate_history, simulation/workflow_sim.py:WorkflowSimulator.wait_for_ai_response, tests/test_external_editor_gui.py:test_button_click_is_received, tests/test_external_editor_gui.py:test_vscode_launches_with_diff_view, tests/test_gui2_parity.py:test_gui2_click_hook_works, tests/test_gui_text_viewer.py:test_text_viewer_state_update, tests/test_live_workflow.py:test_full_live_workflow, tests/test_mma_concurrent_tracks_sim.py:test_mma_concurrent_tracks_execution, tests/test_mma_concurrent_tracks_stress_sim.py:test_mma_concurrent_tracks_stress, tests/test_mma_step_mode_sim.py:test_mma_step_mode_approval_flow, tests/test_rag_phase4_final_verify.py:test_phase4_final_verify, tests/test_rag_phase4_stress.py:test_rag_large_codebase_verification_sim, tests/test_rag_visual_sim.py:test_rag_full_lifecycle_sim, tests/test_saved_presets_sim.py:test_preset_manager_modal, tests/test_saved_presets_sim.py:test_preset_switching, tests/test_system_prompt_sim.py:test_system_prompt_sim, tests/test_ui_cache_controls_sim.py:test_ui_cache_controls, tests/test_undo_redo_sim.py:test_undo_redo_context_mutation, tests/test_undo_redo_sim.py:test_undo_redo_discussion_mutation, tests/test_undo_redo_sim.py:test_undo_redo_lifecycle, tests/test_visual_mma.py:test_visual_mma_components, tests/test_visual_orchestration.py:test_mma_epic_lifecycle, tests/test_visual_sim_gui_ux.py:test_gui_track_creation, tests/test_visual_sim_gui_ux.py:test_gui_ux_event_routing, tests/test_visual_sim_mma_v2.py:_drain_approvals, tests/test_visual_sim_mma_v2.py:test_mma_complete_lifecycle, tests/test_z_negative_flows.py:test_mock_error_result, tests/test_z_negative_flows.py:test_mock_malformed_json, tests/test_z_negative_flows.py:test_mock_timeout]
  """
  return self.post_gui({"action": "click", "item": item, "user_data": user_data})

 def drag(self, src_item: str, dst_item: str) -> dict[str, Any]:
  """
  Simulates a drag and drop operation.
  [C: tests/test_api_hook_client.py:test_drag_success]
  """
  return self.push_event("drag", {"src_item": src_item, "dst_item": dst_item})

 def right_click(self, item: str) -> dict[str, Any]:
  """
  Simulates a right-click on an item.
  [C: tests/test_api_hook_client.py:test_right_click_success]
  """
  return self.push_event("right_click", {"item": item})

 def request_confirmation(self, tool_name: str, args: dict) -> bool | None:
  """
  Pushes a manual confirmation request and waits for response.
  Blocks for up to 60 seconds.
  [C: tests/test_headless_simulation.py:test_mma_track_lifecycle_simulation, tests/test_sync_hooks.py:test_api_ask_client_error, tests/test_sync_hooks.py:test_api_ask_client_method, tests/test_sync_hooks.py:test_api_ask_client_rejection]
  """
  # Long timeout as this waits for human input (60 seconds)
  res = self._make_request('POST', '/api/ask',
   data={'type': 'tool_approval', 'tool': tool_name, 'args': args},
   timeout=60.0)
  return res.get('response') if res else None

 def select_list_item(self, item: str, value: str) -> dict[str, Any]:
  """
  Selects an item in a listbox or combo.
  [C: simulation/workflow_sim.py:WorkflowSimulator.create_discussion, simulation/workflow_sim.py:WorkflowSimulator.switch_discussion, tests/test_api_hook_extensions.py:test_select_list_item_integration, tests/test_live_workflow.py:test_full_live_workflow]
  """
  return self.set_value(item, value)

 def select_tab(self, item: str, value: str) -> dict[str, Any]:
  """
  Selects a specific tab in a tab bar.
  [C: simulation/live_walkthrough.py:main, tests/test_api_hook_extensions.py:test_select_tab_integration]
  """
  return self.set_value(item, value)

#endregion: Input

#region: Patching

 def trigger_patch(self, patch_text: str, file_paths: list[str]) -> dict[str, Any]:
  """Triggers the patch modal to show in the GUI."""
  return self._make_request('POST', '/api/patch/trigger', data={
   "patch_text": patch_text,
   "file_paths": file_paths
  }) or {}

 def apply_patch(self) -> dict[str, Any]:
  """
  Applies the pending patch.
  [C: tests/test_patch_modal.py:test_apply_callback]
  """
  return self._make_request('POST', '/api/patch/apply') or {}

 def reject_patch(self) -> dict[str, Any]:
  """
  Rejects the pending patch.
  [C: tests/test_patch_modal.py:test_reject_callback, tests/test_patch_modal.py:test_reject_patch]
  """
  return self._make_request('POST', '/api/patch/reject') or {}

 def get_patch_status(self) -> dict[str, Any]:
  """Gets the current patch modal status."""
  return self._make_request('GET', '/api/patch/status') or {}

#endregion: Patching

#region: Diagnostics

 def get_indicator_state(self, item_tag: str) -> dict[str, bool]:
  """
  Returns the visibility/active state of a status indicator.
  [C: simulation/live_walkthrough.py:main, tests/test_api_hook_extensions.py:test_get_indicator_state_integration, tests/test_live_workflow.py:test_full_live_workflow]
  """
  val = self.get_value(item_tag)
  return {"shown": bool(val)}

 def get_gui_diagnostics(self) -> dict[str, Any]:
  """
  Retrieves performance and diagnostic metrics.
  [C: tests/test_api_hook_client.py:test_get_performance_success, tests/test_hooks.py:test_live_hook_server_responses, tests/test_selectable_ui.py:test_selectable_label_stability, tests/test_visual_sim_gui_ux.py:test_gui_ux_event_routing]
  """
  return self._make_request('GET', '/api/gui/diagnostics') or {}

 def get_performance(self) -> dict[str, Any]:
  """
  Retrieves performance metrics from the dedicated endpoint.
  [C: tests/test_gui2_performance.py:test_performance_benchmarking, tests/test_gui_performance_requirements.py:test_idle_performance_requirements, tests/test_gui_stress_performance.py:test_comms_volume_stress_performance, tests/test_selectable_ui.py:test_selectable_label_stability]
  """
  return self._make_request('GET', '/api/performance') or {}

 def get_warmup_status(self) -> dict[str, Any]:
  """
  Returns the current warmup status: {pending, completed, failed}.
  [C: tests/test_api_hooks_warmup.py:test_get_warmup_status_calls_correct_endpoint, tests/test_api_hooks_warmup.py:test_get_warmup_status_handles_empty_response, tests/test_api_hooks_warmup.py:test_live_warmup_status_endpoint]
  """
  return self._make_request('GET', '/api/warmup_status') or {}

 def get_warmup_wait(self, timeout: float = 30.0) -> dict[str, Any]:
  """
  Blocks server-side up to `timeout` seconds waiting for the warmup to
  complete, then returns the final status. Useful for external clients
  that need to wait until the system is fully ready before issuing AI
  requests.
  [C: tests/test_api_hooks_warmup.py:test_get_warmup_wait_passes_timeout_as_query_string, tests/test_api_hooks_warmup.py:test_get_warmup_wait_uses_default_timeout_when_unspecified, tests/test_api_hooks_warmup.py:test_get_warmup_wait_handles_empty_response, tests/test_api_hooks_warmup.py:test_live_warmup_wait_endpoint_completes]
  """
  return self._make_request('GET', f'/api/warmup_wait?timeout={timeout}') or {}

 def get_warmup_canaries(self) -> list[dict[str, Any]]:
  """
  Returns per-module import canary records: list of dicts with
  canary_id, module, thread_name, thread_id, submit_ts, start_ts,
  end_ts, elapsed_ms, status, error. Used for debugging which
  worker thread loaded which module and how long it took.
  [C: tests/test_api_hooks_warmup.py:test_get_warmup_canaries_in_live_gui]
  """
  result = self._make_request('GET', '/api/warmup_canaries') or {}
  return result.get("canaries", []) if isinstance(result, dict) else []

 def get_startup_timeline(self) -> dict[str, Any]:
  """
  Returns the startup timeline: dict with init_start_ts, warmup_done_ts,
  first_frame_ts, warmup_ms, first_frame_after_init_ms,
  first_frame_after_warmup_ms. Lets external clients answer
  'did the warmup block the first frame?'.
  [C: tests/test_api_hooks_warmup.py:test_live_startup_timeline_endpoint]
  """
  return self._make_request('GET', '/api/startup_timeline') or {}

#endregion: Diagnostics

#region: Project

 def get_project(self) -> dict[str, Any]:
  """
  Retrieves the current project state.
  [C: simulation/sim_context.py:ContextSimulation.run, tests/test_api_hook_client.py:test_get_project_success, tests/test_gui_context_presets.py:test_gui_context_preset_save_load, tests/test_headless_simulation.py:test_mma_track_lifecycle_simulation, tests/test_hooks.py:test_live_hook_server_responses, tests/test_live_workflow.py:test_full_live_workflow]
  """
  return self._make_request('GET', '/api/project') or {}

 def get_project_switch_status(self) -> dict[str, Any]:
  """
  Returns the project switch status: {in_progress, path, error}.
  - in_progress: True when a project switch is currently scheduled or running
  - path: the path the switch is targeting (or the last completed switch path)
  - error: string error message if the last switch failed; None on success
  Used by tests to wait deterministically for a project switch to complete
  instead of blind-polling the project state.
  [C: tests/test_api_hooks_project_switch.py]
  """
  result = self._make_request('GET', '/api/project_switch_status')
  if not result or not isinstance(result, dict):
   return {"in_progress": False, "path": None, "error": None}
  return result

 def wait_for_project_switch(self, expected_path: str = None, timeout: float = 30.0, poll_interval: float = 0.2) -> dict[str, Any]:
   """
   Blocks until the project switch completes (or fails). Returns the
   final status dict. If expected_path is provided, also waits until the
   reported path matches it (or its basename matches).
   Fails fast (within timeout seconds) with a clear error if:
   - the controller reports an error during the switch
   - the path doesn't reach expected_path within the timeout
   - the controller is hung (in_progress stays True)
   [C: tests/test_live_workflow.py:test_full_live_workflow]
   """
   import os
   start = time.time()
   last_status = {"in_progress": True, "path": None, "error": None}
   expected_basename = os.path.basename(expected_path) if expected_path else None
   while time.time() - start < timeout:
    last_status = self.get_project_switch_status()
    if last_status.get("error"):
     return last_status
    if not last_status.get("in_progress"):
     current_path = last_status.get("path") or ""
     current_basename = os.path.basename(str(current_path))
     path_matches = (expected_path is None
      or current_path == expected_path
      or (expected_basename and current_basename == expected_basename))
     if path_matches:
      return last_status
    time.sleep(poll_interval)
   last_status["timeout"] = True
   return last_status

 def get_io_pool_status(self) -> dict[str, Any]:
  """
  Returns the controller's io_pool status: {idle, inflight}.
  - idle: True if no jobs are currently in-flight (running or queued)
  - inflight: integer count of jobs submitted via submit_io
  Used by tests to wait for the pool to drain before submitting work
  when sharing a live_gui session with prior tests.
  [C: tests/test_api_hook_client.py:test_get_io_pool_status_*]
  """
  result = self._make_request('GET', '/api/io_pool_status')
  if not result or not isinstance(result, dict):
   return {"idle": True, "inflight": 0}
  return result

 def get_gui_health(self) -> dict[str, Any]:
  """
  Returns the controller's GUI health: {healthy, degraded_reason,
  last_assert, io_pool_alive}. Tests should call this before starting
  work and skip / fail fast if `healthy` is False.
  - healthy: True if the GUI main loop is running normally
  - degraded_reason: human-readable description of the failure (if any)
  - last_assert: full traceback of the last ImGui scope mismatch
  - io_pool_alive: True if submit_io is currently functional
  [C: tests/test_api_hook_client_gui_health.py:test_get_gui_health_*]
  """
  result = self._make_request('GET', '/api/gui_health')
  if not result or not isinstance(result, dict):
   return {
    "healthy": True,
    "degraded_reason": None,
    "last_assert": None,
    "io_pool_alive": True,
   }
  return result

 def wait_io_pool_idle(self, timeout: float = 60.0, poll_interval: float = 0.2) -> bool:
  """
  Blocks until the controller's io_pool reports idle=True or timeout.
  Returns True on idle, False on timeout. Use this to ensure prior
  tests' background work has completed before submitting new work.
  [C: tests/test_live_workflow.py:test_full_live_workflow]
  """
  start = time.time()
  while time.time() - start < timeout:
   status = self.get_io_pool_status()
   if status.get("idle"):
    return True
   time.sleep(poll_interval)
  return False

 def post_project(self, project_data: dict) -> dict[str, Any]:
   return last_status

 def post_project(self, project_data: dict) -> dict[str, Any]:
  """
  Updates the current project configuration.
  [C: simulation/sim_context.py:ContextSimulation.run]
  """
  return self._make_request('POST', '/api/project', data=project_data) or {}

#endregion: Project

#region: Context

 def inject_context(self, data: dict) -> dict:
  """
  Injects custom file context into the application.
  [C: tests/test_headless_simulation.py:test_mma_track_lifecycle_simulation]
  """
  return self._make_request('POST', '/api/context/inject', data=data) or {}

 def get_context_state(self) -> dict[str, Any]:
  """
  Retrieves the current file and screenshot context state.
  [C: tests/test_gui_context_presets.py:test_gui_context_preset_save_load]
  """
  return self._make_request('GET', '/api/context/state') or {}

#endregion: Context

#region: Discussion

 def get_session(self) -> dict[str, Any]:
  """
  Retrieves the current discussion session history.
  [C: simulation/ping_pong.py:main, simulation/sim_context.py:ContextSimulation.run, simulation/sim_execution.py:ExecutionSimulation.run, simulation/sim_tools.py:ToolsSimulation.run, simulation/workflow_sim.py:WorkflowSimulator.run_discussion_turn_async, simulation/workflow_sim.py:WorkflowSimulator.wait_for_ai_response, tests/test_api_hook_client.py:test_get_session_success, tests/test_gui_stress_performance.py:test_comms_volume_stress_performance, tests/test_live_workflow.py:test_full_live_workflow, tests/test_rag_phase4_final_verify.py:test_phase4_final_verify, tests/test_rag_phase4_stress.py:test_rag_large_codebase_verification_sim]
  """
  return self._make_request('GET', '/api/session') or {}

 def reset_session(self) -> None:
  """
  Resets the current session via button click.
  [C: src/app_controller.py:AppController._handle_reset_session, src/app_controller.py:AppController.current_model, src/app_controller.py:AppController.current_provider, src/app_controller.py:AppController.init_state, src/gui_2.py:App._render_provider_panel, src/gui_2.py:App._show_menus, src/multi_agent_conductor.py:run_worker_lifecycle, tests/conftest.py:live_gui, tests/conftest.py:reset_ai_client, tests/test_ai_cache_tracking.py:test_gemini_cache_tracking, tests/test_ai_client_cli.py:test_ai_client_send_gemini_cli, tests/test_api_events.py:test_send_emits_events_proper, tests/test_api_events.py:test_send_emits_tool_events, tests/test_deepseek_provider.py:test_deepseek_payload_verification, tests/test_deepseek_provider.py:test_deepseek_reasoner_payload_verification, tests/test_gemini_cli_integration.py:test_gemini_cli_full_integration, tests/test_gemini_cli_integration.py:test_gemini_cli_rejection_and_history, tests/test_gemini_metrics.py:test_get_gemini_cache_stats_with_mock_client, tests/test_headless_simulation.py:test_mma_track_lifecycle_simulation, tests/test_mma_agent_focus_phase1.py:test_append_comms_has_source_tier_key, tests/test_mma_agent_focus_phase1.py:test_append_comms_source_tier_none_when_unset, tests/test_mma_agent_focus_phase1.py:test_append_comms_source_tier_set_when_current_tier_set, tests/test_mma_agent_focus_phase1.py:test_append_comms_source_tier_tier2, tests/test_session_logger_reset.py:test_reset_session, tests/test_token_usage.py:test_token_usage_tracking]
  """
  self.click("btn_reset")

#endregion: Discussion

#region: Analytics

 def get_financial_metrics(self) -> dict[str, Any]:
  """Retrieves token usage and estimated financial cost metrics."""
  return self._make_request('GET', '/api/metrics/financial') or {}

 def get_system_telemetry(self) -> dict[str, Any]:
  """Retrieves system-level telemetry including thread status and event queue size."""
  return self._make_request('GET', '/api/system/telemetry') or {}

#endregion: Analytics

#region: MMA

 def get_node_status(self, node_id: str) -> dict[str, Any]:
  """
  Retrieves status for a specific node in the MMA DAG.
  [C: tests/test_api_hook_client.py:test_get_node_status]
  """
  return self._make_request('GET', f'/api/mma/node/{node_id}') or {}

 def get_mma_status(self) -> dict[str, Any]:
  """
  Retrieves the dedicated MMA engine status.
  [C: tests/test_headless_simulation.py:test_mma_track_lifecycle_simulation, tests/test_live_workflow.py:test_full_live_workflow, tests/test_mma_concurrent_tracks_sim.py:_poll_mma_status, tests/test_mma_concurrent_tracks_sim.py:test_mma_concurrent_tracks_execution, tests/test_mma_concurrent_tracks_stress_sim.py:test_mma_concurrent_tracks_stress, tests/test_mma_step_mode_sim.py:_poll_mma_status, tests/test_mma_step_mode_sim.py:test_mma_step_mode_approval_flow, tests/test_visual_mma.py:test_visual_mma_components, tests/test_visual_orchestration.py:test_mma_epic_lifecycle, tests/test_visual_sim_gui_ux.py:test_gui_ux_event_routing, tests/test_visual_sim_mma_v2.py:_poll]
  """
  return self._make_request('GET', '/api/gui/mma_status') or {}

 def get_mma_workers(self) -> dict[str, Any]:
  """
  Retrieves status for all active MMA workers.
  [C: tests/test_headless_simulation.py:test_mma_track_lifecycle_simulation, tests/test_mma_concurrent_tracks_sim.py:test_mma_concurrent_tracks_execution, tests/test_mma_concurrent_tracks_stress_sim.py:_poll_mma_workers]
  """
  return self._make_request('GET', '/api/mma/workers') or {}

 def spawn_mma_worker(self, data: dict) -> dict:
  """
  
    Spawns a new MMA worker with the provided configuration.
    [C: tests/test_headless_simulation.py:test_mma_track_lifecycle_simulation]
  """
  return self._make_request('POST', '/api/mma/workers/spawn', data=data) or {}

 def kill_mma_worker(self, worker_id: str) -> dict:
  """Kills an active MMA worker by its ID."""
  return self._make_request('POST', '/api/mma/workers/kill', data={"worker_id": worker_id}) or {}

 def pause_mma_pipeline(self) -> dict:
  """
  Pauses the MMA execution pipeline.
  [C: tests/test_mma_step_mode_sim.py:test_mma_step_mode_approval_flow]
  """
  return self._make_request('POST', '/api/mma/pipeline/pause') or {}

 def resume_mma_pipeline(self) -> dict:
  """Resumes the MMA execution pipeline."""
  return self._make_request('POST', '/api/mma/pipeline/resume') or {}

 def mutate_mma_dag(self, data: dict) -> dict:
  """
  Mutates the MMA DAG (Directed Acyclic Graph) structure.
  [C: tests/test_headless_simulation.py:test_mma_track_lifecycle_simulation]
  """
  return self._make_request('POST', '/api/mma/dag/mutate', data=data) or {}

 def approve_mma_ticket(self, ticket_id: str) -> dict:
  """
  Manually approves a specific ticket for execution in Step Mode.
  [C: tests/test_mma_step_mode_sim.py:test_mma_step_mode_approval_flow]
  """
  return self._make_request('POST', '/api/mma/ticket/approve', data={"ticket_id": ticket_id}) or {}

#endregion: MMA
