from src.command_palette import Command, ScoredCommand, fuzzy_match


def _cmd(id: str, title: str) -> Command:
 return Command(id=id, title=title, category="test")


def test_fuzzy_match_prefix_ranks_first():
 candidates = [
  _cmd("find", "Find in Selection"),
  _cmd("fold", "Fold All"),
  _cmd("config", "Configure Settings"),
 ]
 results = fuzzy_match("fin", candidates, top_n=10)
 assert len(results) > 0
 assert results[0].command.id == "find"
 assert results[0].score > 0.5


def test_fuzzy_match_subsequence_match():
 candidates = [_cmd("x", "Find")]
 results = fuzzy_match("fd", candidates, top_n=10)
 assert len(results) == 1
 assert results[0].command.id == "x"


def test_fuzzy_match_no_match_returns_empty():
 candidates = [_cmd("x", "foo bar")]
 results = fuzzy_match("xyz", candidates, top_n=10)
 assert results == []


def test_fuzzy_match_top_n_limits_results():
 candidates = [_cmd(f"cmd_{i}", f"Command {i}") for i in range(50)]
 results = fuzzy_match("cmd", candidates, top_n=10)
 assert len(results) == 10


def test_fuzzy_match_score_higher_for_exact_prefix():
 candidates = [
  _cmd("a", "find"),
  _cmd("b", "Configure Find Settings"),
 ]
 results = fuzzy_match("fin", candidates, top_n=10)
 assert results[0].command.id == "a"


def test_commands_registry_has_core_commands():
 from src.commands import registry
 all_ids = {c.id for c in registry.all()}
 assert "reset_session" in all_ids
 assert "clear_discussion" in all_ids
 assert "trigger_hot_reload" in all_ids
 assert "show_documentation" in all_ids


def test_commands_registry_has_view_toggles():
 """The palette should expose all window toggle commands for keyboard ergonomics."""
 from src.commands import registry
 all_ids = {c.id for c in registry.all()}
 expected_toggles = [
  "toggle_text_viewer",
  "toggle_diagnostics",
  "toggle_usage_analytics",
  "toggle_context_preview",
  "toggle_tier1_strategy",
  "toggle_tier2_tech_lead",
  "toggle_tier3_workers",
  "toggle_tier4_qa",
  "toggle_external_tools",
  "toggle_shader_editor",
  "toggle_undo_redo_history",
  "toggle_command_palette",
 ]
 for cmd_id in expected_toggles:
  assert cmd_id in all_ids, f"Missing window toggle command: {cmd_id}"


def test_commands_registry_has_theme_commands():
 from src.commands import registry
 all_ids = {c.id for c in registry.all()}
 assert "switch_to_dark_theme" in all_ids
 assert "switch_to_light_theme" in all_ids
 assert "switch_to_nerv_theme" in all_ids
 assert "cycle_theme" in all_ids


def test_commands_registry_has_layout_commands():
 from src.commands import registry
 all_ids = {c.id for c in registry.all()}
 assert "show_all_panels" in all_ids
 assert "hide_all_panels" in all_ids
 assert "save_workspace_profile" in all_ids


def test_commands_registry_has_undo_redo_commands():
 from src.commands import registry
 all_ids = {c.id for c in registry.all()}
 assert "undo" in all_ids
 assert "redo" in all_ids


def test_all_commands_have_actions():
 """Every registered command must have a callable action."""
 from src.commands import registry
 for cmd in registry.all():
  assert cmd.id, f"Command missing id: {cmd}"
  assert cmd.title, f"Command {cmd.id} missing title"
  assert cmd.category, f"Command {cmd.id} missing category"
  assert cmd.action is not None, f"Command {cmd.id} missing action"
  assert callable(cmd.action), f"Command {cmd.id} action is not callable"


def test_toggle_helpers_are_safe_with_missing_state():
 """The _toggle_window and _toggle_attr helpers must not raise on missing state."""
 from unittest.mock import MagicMock
 from src.commands import _toggle_window, _toggle_attr

 bare_app = MagicMock(spec=[])
 _toggle_window(bare_app, "Anything")
 _toggle_attr(bare_app, "anything")

 partial_app = MagicMock()
 partial_app.show_windows = {}
 _toggle_window(partial_app, "NewPanel")
 assert partial_app.show_windows["NewPanel"] is True


def test_undo_command_routes_to_handler():
 from unittest.mock import MagicMock
 from src.commands import registry

 app = MagicMock()
 app._handle_undo = MagicMock()

 undo_cmd = registry.get("undo")
 assert undo_cmd is not None
 undo_cmd.action(app)
 app._handle_undo.assert_called_once()
