yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
wasm_control_api.cc
Go to the documentation of this file.
1// clang-format off
2#ifdef __EMSCRIPTEN__
3
5
6#include <emscripten.h>
7#include <emscripten/bind.h>
8
9#include "absl/strings/str_format.h"
11#include "app/editor/editor.h"
21#include "rom/rom.h"
22#include "nlohmann/json.hpp"
23#include "util/log.h"
24#include "zelda3/dungeon/room.h"
27
28namespace yaze {
29namespace app {
30namespace platform {
31
32// Static member initialization
33editor::EditorManager* WasmControlApi::editor_manager_ = nullptr;
34bool WasmControlApi::initialized_ = false;
35
36// ============================================================================
37// JavaScript Bindings Setup
38// ============================================================================
39
40EM_JS(void, SetupYazeControlApi, (), {
41 if (typeof Module === 'undefined') return;
42
43 // Create unified window.yaze namespace if not exists
44 if (!window.yaze) {
45 window.yaze = {};
46 }
47
48 // Control API namespace
49 window.yaze.control = {
50 // Editor control
51 switchEditor: function(editorName) {
52 if (Module.controlSwitchEditor) {
53 try { return JSON.parse(Module.controlSwitchEditor(editorName)); }
54 catch(e) { return {error: e.message}; }
55 }
56 return {error: "API not ready"};
57 },
58
59 getCurrentEditor: function() {
60 if (Module.controlGetCurrentEditor) {
61 try { return JSON.parse(Module.controlGetCurrentEditor()); }
62 catch(e) { return {error: e.message}; }
63 }
64 return {error: "API not ready"};
65 },
66
67 getAvailableEditors: function() {
68 if (Module.controlGetAvailableEditors) {
69 try { return JSON.parse(Module.controlGetAvailableEditors()); }
70 catch(e) { return {error: e.message}; }
71 }
72 return {error: "API not ready"};
73 },
74
75 // Panel control (card naming kept for backward compatibility)
76 openPanel: function(cardId) {
77 if (Module.controlOpenPanel) {
78 try { return JSON.parse(Module.controlOpenPanel(cardId)); }
79 catch(e) { return {error: e.message}; }
80 }
81 return {error: "API not ready"};
82 },
83
84 closePanel: function(cardId) {
85 if (Module.controlClosePanel) {
86 try { return JSON.parse(Module.controlClosePanel(cardId)); }
87 catch(e) { return {error: e.message}; }
88 }
89 return {error: "API not ready"};
90 },
91
92 togglePanel: function(cardId) {
93 if (Module.controlTogglePanel) {
94 try { return JSON.parse(Module.controlTogglePanel(cardId)); }
95 catch(e) { return {error: e.message}; }
96 }
97 return {error: "API not ready"};
98 },
99
100 // Panel aliases
101 openPanel: function(panelId) { return this.openPanel(panelId); },
102 closePanel: function(panelId) { return this.closePanel(panelId); },
103 togglePanel: function(panelId) { return this.togglePanel(panelId); },
104
105 getVisiblePanels: function() {
106 if (Module.controlGetVisiblePanels) {
107 try { return JSON.parse(Module.controlGetVisiblePanels()); }
108 catch(e) { return {error: e.message}; }
109 }
110 return {error: "API not ready"};
111 },
112
113 getVisiblePanels: function() { return this.getVisiblePanels(); },
114
115 getAvailablePanels: function() {
116 if (Module.controlGetAvailablePanels) {
117 try { return JSON.parse(Module.controlGetAvailablePanels()); }
118 catch(e) { return {error: e.message}; }
119 }
120 return {error: "API not ready"};
121 },
122
123 getAvailablePanels: function() { return this.getAvailablePanels(); },
124
125 getPanelsInCategory: function(category) {
126 if (Module.controlGetPanelsInCategory) {
127 try { return JSON.parse(Module.controlGetPanelsInCategory(category)); }
128 catch(e) { return {error: e.message}; }
129 }
130 return {error: "API not ready"};
131 },
132
133 getPanelsInCategory: function(category) { return this.getPanelsInCategory(category); },
134
135 showAllPanels: function() {
136 if (Module.controlShowAllPanels) {
137 try { return JSON.parse(Module.controlShowAllPanels()); }
138 catch(e) { return {error: e.message}; }
139 }
140 return {error: "API not ready"};
141 },
142
143 showAllPanels: function() { return this.showAllPanels(); },
144
145 hideAllPanels: function() {
146 if (Module.controlHideAllPanels) {
147 try { return JSON.parse(Module.controlHideAllPanels()); }
148 catch(e) { return {error: e.message}; }
149 }
150 return {error: "API not ready"};
151 },
152
153 hideAllPanels: function() { return this.hideAllPanels(); },
154
155 showAllPanelsInCategory: function(category) {
156 if (Module.controlShowAllPanelsInCategory) {
157 try { return JSON.parse(Module.controlShowAllPanelsInCategory(category)); }
158 catch(e) { return {error: e.message}; }
159 }
160 return {error: "API not ready"};
161 },
162
163 showAllPanelsInCategory: function(category) { return this.showAllPanelsInCategory(category); },
164
165 hideAllPanelsInCategory: function(category) {
166 if (Module.controlHideAllPanelsInCategory) {
167 try { return JSON.parse(Module.controlHideAllPanelsInCategory(category)); }
168 catch(e) { return {error: e.message}; }
169 }
170 return {error: "API not ready"};
171 },
172
173 hideAllPanelsInCategory: function(category) { return this.hideAllPanelsInCategory(category); },
174
175 showOnlyPanel: function(cardId) {
176 if (Module.controlShowOnlyPanel) {
177 try { return JSON.parse(Module.controlShowOnlyPanel(cardId)); }
178 catch(e) { return {error: e.message}; }
179 }
180 return {error: "API not ready"};
181 },
182
183 showOnlyPanel: function(panelId) { return this.showOnlyPanel(panelId); },
184
185 // Layout control
186 setPanelLayout: function(layoutName) {
187 if (Module.controlSetPanelLayout) {
188 try { return JSON.parse(Module.controlSetPanelLayout(layoutName)); }
189 catch(e) { return {error: e.message}; }
190 }
191 return {error: "API not ready"};
192 },
193
194 setPanelLayout: function(layoutName) { return this.setPanelLayout(layoutName); },
195
196 getAvailableLayouts: function() {
197 if (Module.controlGetAvailableLayouts) {
198 try { return JSON.parse(Module.controlGetAvailableLayouts()); }
199 catch(e) { return {error: e.message}; }
200 }
201 return {error: "API not ready"};
202 },
203
204 saveCurrentLayout: function(layoutName) {
205 if (Module.controlSaveCurrentLayout) {
206 try { return JSON.parse(Module.controlSaveCurrentLayout(layoutName)); }
207 catch(e) { return {error: e.message}; }
208 }
209 return {error: "API not ready"};
210 },
211
212 // Menu/UI actions
213 triggerMenuAction: function(actionPath) {
214 if (Module.controlTriggerMenuAction) {
215 try { return JSON.parse(Module.controlTriggerMenuAction(actionPath)); }
216 catch(e) { return {error: e.message}; }
217 }
218 return {error: "API not ready"};
219 },
220
221 getAvailableMenuActions: function() {
222 if (Module.controlGetAvailableMenuActions) {
223 try { return JSON.parse(Module.controlGetAvailableMenuActions()); }
224 catch(e) { return {error: e.message}; }
225 }
226 return {error: "API not ready"};
227 },
228
229 // Session control
230 getSessionInfo: function() {
231 if (Module.controlGetSessionInfo) {
232 try { return JSON.parse(Module.controlGetSessionInfo()); }
233 catch(e) { return {error: e.message}; }
234 }
235 return {error: "API not ready"};
236 },
237
238 createSession: function() {
239 if (Module.controlCreateSession) {
240 try { return JSON.parse(Module.controlCreateSession()); }
241 catch(e) { return {error: e.message}; }
242 }
243 return {error: "API not ready"};
244 },
245
246 switchSession: function(sessionIndex) {
247 if (Module.controlSwitchSession) {
248 try { return JSON.parse(Module.controlSwitchSession(sessionIndex)); }
249 catch(e) { return {error: e.message}; }
250 }
251 return {error: "API not ready"};
252 },
253
254 // ROM control
255 getRomStatus: function() {
256 if (Module.controlGetRomStatus) {
257 try { return JSON.parse(Module.controlGetRomStatus()); }
258 catch(e) { return {error: e.message}; }
259 }
260 return {error: "API not ready"};
261 },
262
263 readRomBytes: function(address, count) {
264 count = count || 16;
265 if (Module.controlReadRomBytes) {
266 try { return JSON.parse(Module.controlReadRomBytes(address, count)); }
267 catch(e) { return {error: e.message}; }
268 }
269 return {error: "API not ready"};
270 },
271
272 writeRomBytes: function(address, bytes) {
273 if (Module.controlWriteRomBytes) {
274 try { return JSON.parse(Module.controlWriteRomBytes(address, JSON.stringify(bytes))); }
275 catch(e) { return {error: e.message}; }
276 }
277 return {error: "API not ready"};
278 },
279
280 saveRom: function() {
281 if (Module.controlSaveRom) {
282 try { return JSON.parse(Module.controlSaveRom()); }
283 catch(e) { return {error: e.message}; }
284 }
285 return {error: "API not ready"};
286 },
287
288 // Utility
289 isReady: function() {
290 return Module.controlIsReady ? Module.controlIsReady() : false;
291 },
292
293 // Platform info - returns detected platform and keyboard modifier names
294 getPlatformInfo: function() {
295 if (Module.controlGetPlatformInfo) {
296 try { return JSON.parse(Module.controlGetPlatformInfo()); }
297 catch(e) { return {error: e.message}; }
298 }
299 return {error: "API not ready"};
300 }
301 };
302
303 // Editor State API namespace (for LLM agents and automation)
304 window.yaze.editor = {
305 getSnapshot: function() {
306 if (Module.editorGetSnapshot) {
307 try { return JSON.parse(Module.editorGetSnapshot()); }
308 catch(e) { return {error: e.message}; }
309 }
310 return {error: "API not ready"};
311 },
312
313 getCurrentRoom: function() {
314 if (Module.editorGetCurrentDungeonRoom) {
315 try { return JSON.parse(Module.editorGetCurrentDungeonRoom()); }
316 catch(e) { return {error: e.message}; }
317 }
318 return {error: "API not ready"};
319 },
320
321 getCurrentMap: function() {
322 if (Module.editorGetCurrentOverworldMap) {
323 try { return JSON.parse(Module.editorGetCurrentOverworldMap()); }
324 catch(e) { return {error: e.message}; }
325 }
326 return {error: "API not ready"};
327 },
328
329 getSelection: function() {
330 if (Module.editorGetSelection) {
331 try { return JSON.parse(Module.editorGetSelection()); }
332 catch(e) { return {error: e.message}; }
333 }
334 return {error: "API not ready"};
335 }
336 };
337
338 // Data API namespace (read-only access to ROM data)
339 window.yaze.data = {
340 // Dungeon data
341 getRoomTiles: function(roomId) {
342 if (Module.dataGetRoomTileData) {
343 try { return JSON.parse(Module.dataGetRoomTileData(roomId)); }
344 catch(e) { return {error: e.message}; }
345 }
346 return {error: "API not ready"};
347 },
348
349 getRoomObjects: function(roomId) {
350 if (Module.dataGetRoomObjects) {
351 try { return JSON.parse(Module.dataGetRoomObjects(roomId)); }
352 catch(e) { return {error: e.message}; }
353 }
354 return {error: "API not ready"};
355 },
356
357 getRoomProperties: function(roomId) {
358 if (Module.dataGetRoomProperties) {
359 try { return JSON.parse(Module.dataGetRoomProperties(roomId)); }
360 catch(e) { return {error: e.message}; }
361 }
362 return {error: "API not ready"};
363 },
364
365 // Overworld data
366 getMapTiles: function(mapId) {
367 if (Module.dataGetMapTileData) {
368 try { return JSON.parse(Module.dataGetMapTileData(mapId)); }
369 catch(e) { return {error: e.message}; }
370 }
371 return {error: "API not ready"};
372 },
373
374 getMapEntities: function(mapId) {
375 if (Module.dataGetMapEntities) {
376 try { return JSON.parse(Module.dataGetMapEntities(mapId)); }
377 catch(e) { return {error: e.message}; }
378 }
379 return {error: "API not ready"};
380 },
381
382 getMapProperties: function(mapId) {
383 if (Module.dataGetMapProperties) {
384 try { return JSON.parse(Module.dataGetMapProperties(mapId)); }
385 catch(e) { return {error: e.message}; }
386 }
387 return {error: "API not ready"};
388 },
389
390 // Palette data
391 getPalette: function(groupName, paletteId) {
392 if (Module.dataGetPaletteData) {
393 try { return JSON.parse(Module.dataGetPaletteData(groupName, paletteId)); }
394 catch(e) { return {error: e.message}; }
395 }
396 return {error: "API not ready"};
397 },
398
399 getPaletteGroups: function() {
400 if (Module.dataListPaletteGroups) {
401 try { return JSON.parse(Module.dataListPaletteGroups()); }
402 catch(e) { return {error: e.message}; }
403 }
404 return {error: "API not ready"};
405 }
406 };
407
408 // Agent API namespace (for AI/LLM agent integration)
409 window.yaze.agent = {
410 // Send a message to the agent chat
411 sendMessage: function(message) {
412 if (Module.agentSendMessage) {
413 try { return JSON.parse(Module.agentSendMessage(message)); }
414 catch(e) { return {error: e.message}; }
415 }
416 return {error: "API not ready"};
417 },
418
419 // Get chat history
420 getChatHistory: function() {
421 if (Module.agentGetChatHistory) {
422 try { return JSON.parse(Module.agentGetChatHistory()); }
423 catch(e) { return {error: e.message}; }
424 }
425 return {error: "API not ready"};
426 },
427
428 // Get agent configuration
429 getConfig: function() {
430 if (Module.agentGetConfig) {
431 try { return JSON.parse(Module.agentGetConfig()); }
432 catch(e) { return {error: e.message}; }
433 }
434 return {error: "API not ready"};
435 },
436
437 // Set agent configuration
438 setConfig: function(config) {
439 if (Module.agentSetConfig) {
440 try { return JSON.parse(Module.agentSetConfig(JSON.stringify(config))); }
441 catch(e) { return {error: e.message}; }
442 }
443 return {error: "API not ready"};
444 },
445
446 // Get available AI providers
447 getProviders: function() {
448 if (Module.agentGetProviders) {
449 try { return JSON.parse(Module.agentGetProviders()); }
450 catch(e) { return {error: e.message}; }
451 }
452 return {error: "API not ready"};
453 },
454
455 // Get proposal list
456 getProposals: function() {
457 if (Module.agentGetProposals) {
458 try { return JSON.parse(Module.agentGetProposals()); }
459 catch(e) { return {error: e.message}; }
460 }
461 return {error: "API not ready"};
462 },
463
464 // Accept a proposal
465 acceptProposal: function(proposalId) {
466 if (Module.agentAcceptProposal) {
467 try { return JSON.parse(Module.agentAcceptProposal(proposalId)); }
468 catch(e) { return {error: e.message}; }
469 }
470 return {error: "API not ready"};
471 },
472
473 // Reject a proposal
474 rejectProposal: function(proposalId) {
475 if (Module.agentRejectProposal) {
476 try { return JSON.parse(Module.agentRejectProposal(proposalId)); }
477 catch(e) { return {error: e.message}; }
478 }
479 return {error: "API not ready"};
480 },
481
482 // Get proposal details
483 getProposalDetails: function(proposalId) {
484 if (Module.agentGetProposalDetails) {
485 try { return JSON.parse(Module.agentGetProposalDetails(proposalId)); }
486 catch(e) { return {error: e.message}; }
487 }
488 return {error: "API not ready"};
489 },
490
491 // Open/close agent sidebar
492 openSidebar: function() {
493 if (Module.agentOpenSidebar) {
494 try { return JSON.parse(Module.agentOpenSidebar()); }
495 catch(e) { return {error: e.message}; }
496 }
497 return {error: "API not ready"};
498 },
499
500 closeSidebar: function() {
501 if (Module.agentCloseSidebar) {
502 try { return JSON.parse(Module.agentCloseSidebar()); }
503 catch(e) { return {error: e.message}; }
504 }
505 return {error: "API not ready"};
506 },
507
508 // Check if agent is ready
509 isReady: function() {
510 return Module.agentIsReady ? Module.agentIsReady() : false;
511 }
512 };
513
514 console.log("[yaze] window.yaze.control API initialized");
515 console.log("[yaze] window.yaze.editor API initialized");
516 console.log("[yaze] window.yaze.data API initialized");
517 console.log("[yaze] window.yaze.agent API initialized");
518});
519
520// ============================================================================
521// Initialization
522// ============================================================================
523
524void WasmControlApi::Initialize(editor::EditorManager* editor_manager) {
525 editor_manager_ = editor_manager;
526 initialized_ = (editor_manager_ != nullptr);
527
528 if (initialized_) {
529 SetupJavaScriptBindings();
530 LOG_INFO("WasmControlApi", "Control API initialized");
531 }
532}
533
534bool WasmControlApi::IsReady() {
535 return initialized_ && editor_manager_ != nullptr;
536}
537
538void WasmControlApi::SetupJavaScriptBindings() {
539 SetupYazeControlApi();
540}
541
542// ============================================================================
543// Helper Methods
544// ============================================================================
545
546editor::PanelManager* WasmControlApi::GetPanelRegistry() {
547 if (!IsReady() || !editor_manager_) {
548 return nullptr;
549 }
550 return &editor_manager_->card_registry();
551}
552
553std::string WasmControlApi::EditorTypeToString(int type) {
554 if (type >= 0 && type < static_cast<int>(editor::kEditorNames.size())) {
555 return editor::kEditorNames[type];
556 }
557 return "Unknown";
558}
559
560int WasmControlApi::StringToEditorType(const std::string& name) {
561 for (size_t i = 0; i < editor::kEditorNames.size(); ++i) {
562 if (editor::kEditorNames[i] == name) {
563 return static_cast<int>(i);
564 }
565 }
566 return 0; // Unknown
567}
568
569// ============================================================================
570// Editor Control Implementation
571// ============================================================================
572
573std::string WasmControlApi::SwitchEditor(const std::string& editor_name) {
574 nlohmann::json result;
575
576 if (!IsReady()) {
577 result["success"] = false;
578 result["error"] = "Control API not initialized";
579 return result.dump();
580 }
581
582 int editor_type = StringToEditorType(editor_name);
583 if (editor_type == 0 && editor_name != "Unknown") {
584 result["success"] = false;
585 result["error"] = "Unknown editor: " + editor_name;
586 return result.dump();
587 }
588
589 editor_manager_->SwitchToEditor(static_cast<editor::EditorType>(editor_type));
590
591 result["success"] = true;
592 result["editor"] = editor_name;
593 return result.dump();
594}
595
596std::string WasmControlApi::GetCurrentEditor() {
597 nlohmann::json result;
598
599 if (!IsReady()) {
600 result["error"] = "Control API not initialized";
601 return result.dump();
602 }
603
604 auto* current = editor_manager_->GetCurrentEditor();
605 if (current) {
606 result["name"] = EditorTypeToString(static_cast<int>(current->type()));
607 result["type"] = static_cast<int>(current->type());
608 result["active"] = *current->active();
609 } else {
610 result["name"] = "None";
611 result["type"] = 0;
612 result["active"] = false;
613 }
614
615 return result.dump();
616}
617
618std::string WasmControlApi::GetAvailableEditors() {
619 nlohmann::json result = nlohmann::json::array();
620
621 for (size_t i = 1; i < editor::kEditorNames.size(); ++i) { // Skip "Unknown"
622 nlohmann::json editor_info;
623 editor_info["name"] = editor::kEditorNames[i];
624 editor_info["type"] = static_cast<int>(i);
625 result.push_back(editor_info);
626 }
627
628 return result.dump();
629}
630
631// ============================================================================
632// Panel Control Implementation
633// ============================================================================
634
635std::string WasmControlApi::OpenPanel(const std::string& card_id) {
636 nlohmann::json result;
637
638 if (!IsReady()) {
639 result["success"] = false;
640 result["error"] = "Control API not initialized";
641 return result.dump();
642 }
643
644 auto* registry = GetPanelRegistry();
645 if (registry) {
646 // Use default session ID (0) for WASM single-session mode
647 constexpr size_t session_id = 0;
648 bool found = registry->ShowPanel(session_id, card_id);
649
650 result["success"] = found;
651 result["card_id"] = card_id;
652 result["visible"] = true;
653 if (!found) {
654 result["error"] = "Panel not found";
655 }
656 } else {
657 result["success"] = false;
658 result["error"] = "Panel registry not available";
659 }
660
661 LOG_INFO("WasmControlApi", "OpenPanel: %s", card_id.c_str());
662 return result.dump();
663}
664
665std::string WasmControlApi::ClosePanel(const std::string& card_id) {
666 nlohmann::json result;
667
668 if (!IsReady()) {
669 result["success"] = false;
670 result["error"] = "Control API not initialized";
671 return result.dump();
672 }
673
674 auto* registry = GetPanelRegistry();
675 if (registry) {
676 constexpr size_t session_id = 0;
677 bool found = registry->HidePanel(session_id, card_id);
678
679 result["success"] = found;
680 result["card_id"] = card_id;
681 result["visible"] = false;
682 if (!found) {
683 result["error"] = "Panel not found";
684 }
685 } else {
686 result["success"] = false;
687 result["error"] = "Panel registry not available";
688 }
689
690 LOG_INFO("WasmControlApi", "ClosePanel: %s", card_id.c_str());
691 return result.dump();
692}
693
694std::string WasmControlApi::TogglePanel(const std::string& card_id) {
695 nlohmann::json result;
696
697 if (!IsReady()) {
698 result["success"] = false;
699 result["error"] = "Control API not initialized";
700 return result.dump();
701 }
702
703 auto* registry = GetPanelRegistry();
704 if (registry) {
705 constexpr size_t session_id = 0;
706 bool found = registry->TogglePanel(session_id, card_id);
707
708 result["success"] = found;
709 result["card_id"] = card_id;
710 if (!found) {
711 result["error"] = "Panel not found";
712 } else {
713 result["visible"] = registry->IsPanelVisible(session_id, card_id);
714 }
715 } else {
716 result["success"] = false;
717 result["error"] = "Panel registry not available";
718 }
719
720 LOG_INFO("WasmControlApi", "TogglePanel: %s", card_id.c_str());
721 return result.dump();
722}
723
724std::string WasmControlApi::GetVisiblePanels() {
725 nlohmann::json result = nlohmann::json::array();
726
727 if (!IsReady()) {
728 return result.dump();
729 }
730
731 auto* registry = GetPanelRegistry();
732 if (!registry) {
733 return result.dump();
734 }
735
736 // Use default session ID (0) for WASM single-session mode
737 constexpr size_t session_id = 0;
738 auto card_ids = registry->GetPanelsInSession(session_id);
739 for (const auto& card_id : card_ids) {
740 // Extract base card ID (remove session prefix like "s0.")
741 std::string base_id = card_id;
742 if (base_id.size() > 3 && base_id[0] == 's' && base_id[2] == '.') {
743 base_id = base_id.substr(3);
744 }
745 if (registry->IsPanelVisible(session_id, base_id)) {
746 result.push_back(base_id);
747 }
748 }
749
750 return result.dump();
751}
752
753std::string WasmControlApi::GetAvailablePanels() {
754 nlohmann::json result = nlohmann::json::array();
755
756 if (!IsReady()) {
757 return result.dump();
758 }
759
760 auto* registry = GetPanelRegistry();
761 if (!registry) {
762 return result.dump();
763 }
764
765 // Use default session ID (0) for WASM single-session mode
766 constexpr size_t session_id = 0;
767 auto categories = registry->GetAllCategories(session_id);
768
769 for (const auto& category : categories) {
770 auto panels = registry->GetPanelsInCategory(session_id, category);
771 for (const auto& panel : panels) {
772 nlohmann::json card_json;
773 card_json["id"] = panel.card_id;
774 card_json["display_name"] = panel.display_name;
775 card_json["window_title"] = panel.window_title;
776 card_json["icon"] = panel.icon;
777 card_json["category"] = panel.category;
778 card_json["priority"] = panel.priority;
779 card_json["visible"] = registry->IsPanelVisible(session_id, panel.card_id);
780 card_json["shortcut_hint"] = panel.shortcut_hint;
781 if (panel.enabled_condition) {
782 card_json["enabled"] = panel.enabled_condition();
783 } else {
784 card_json["enabled"] = true;
785 }
786 result.push_back(card_json);
787 }
788 }
789
790 return result.dump();
791}
792
793std::string WasmControlApi::GetPanelsInCategory(const std::string& category) {
794 nlohmann::json result = nlohmann::json::array();
795
796 if (!IsReady()) {
797 return result.dump();
798 }
799
800 auto* registry = GetPanelRegistry();
801 if (!registry) {
802 return result.dump();
803 }
804
805 // Use default session ID (0) for WASM single-session mode
806 constexpr size_t session_id = 0;
807 auto panels = registry->GetPanelsInCategory(session_id, category);
808
809 for (const auto& panel : panels) {
810 nlohmann::json card_json;
811 card_json["id"] = panel.card_id;
812 card_json["display_name"] = panel.display_name;
813 card_json["window_title"] = panel.window_title;
814 card_json["icon"] = panel.icon;
815 card_json["category"] = panel.category;
816 card_json["priority"] = panel.priority;
817 card_json["visible"] = registry->IsPanelVisible(session_id, panel.card_id);
818 result.push_back(card_json);
819 }
820
821 return result.dump();
822}
823
824std::string WasmControlApi::ShowAllPanels() {
825 nlohmann::json result;
826
827 if (!IsReady()) {
828 result["success"] = false;
829 result["error"] = "Control API not initialized";
830 return result.dump();
831 }
832
833 auto* registry = GetPanelRegistry();
834 if (registry) {
835 constexpr size_t session_id = 0;
836 registry->ShowAllPanelsInSession(session_id);
837 result["success"] = true;
838 } else {
839 result["success"] = false;
840 result["error"] = "Panel registry not available";
841 }
842
843 return result.dump();
844}
845
846std::string WasmControlApi::HideAllPanels() {
847 nlohmann::json result;
848
849 if (!IsReady()) {
850 result["success"] = false;
851 result["error"] = "Control API not initialized";
852 return result.dump();
853 }
854
855 auto* registry = GetPanelRegistry();
856 if (registry) {
857 constexpr size_t session_id = 0;
858 registry->HideAllPanelsInSession(session_id);
859 result["success"] = true;
860 } else {
861 result["success"] = false;
862 result["error"] = "Panel registry not available";
863 }
864
865 return result.dump();
866}
867
868std::string WasmControlApi::ShowAllPanelsInCategory(const std::string& category) {
869 nlohmann::json result;
870
871 if (!IsReady()) {
872 result["success"] = false;
873 result["error"] = "Control API not initialized";
874 return result.dump();
875 }
876
877 auto* registry = GetPanelRegistry();
878 if (registry) {
879 constexpr size_t session_id = 0;
880 registry->ShowAllPanelsInCategory(session_id, category);
881 result["success"] = true;
882 result["category"] = category;
883 } else {
884 result["success"] = false;
885 result["error"] = "Panel registry not available";
886 }
887
888 return result.dump();
889}
890
891std::string WasmControlApi::HideAllPanelsInCategory(const std::string& category) {
892 nlohmann::json result;
893
894 if (!IsReady()) {
895 result["success"] = false;
896 result["error"] = "Control API not initialized";
897 return result.dump();
898 }
899
900 auto* registry = GetPanelRegistry();
901 if (registry) {
902 constexpr size_t session_id = 0;
903 registry->HideAllPanelsInCategory(session_id, category);
904 result["success"] = true;
905 result["category"] = category;
906 } else {
907 result["success"] = false;
908 result["error"] = "Panel registry not available";
909 }
910
911 return result.dump();
912}
913
914std::string WasmControlApi::ShowOnlyPanel(const std::string& card_id) {
915 nlohmann::json result;
916
917 if (!IsReady()) {
918 result["success"] = false;
919 result["error"] = "Control API not initialized";
920 return result.dump();
921 }
922
923 auto* registry = GetPanelRegistry();
924 if (registry) {
925 constexpr size_t session_id = 0;
926 registry->ShowOnlyPanel(session_id, card_id);
927 result["success"] = true;
928 result["card_id"] = card_id;
929 } else {
930 result["success"] = false;
931 result["error"] = "Panel registry not available";
932 }
933
934 return result.dump();
935}
936
937// ============================================================================
938// Layout Control Implementation
939// ============================================================================
940
941std::string WasmControlApi::SetPanelLayout(const std::string& layout_name) {
942 nlohmann::json result;
943
944 if (!IsReady()) {
945 result["success"] = false;
946 result["error"] = "Control API not initialized";
947 return result.dump();
948 }
949
950 // TODO: Implement layout presets
951 result["success"] = true;
952 result["layout"] = layout_name;
953
954 LOG_INFO("WasmControlApi", "SetPanelLayout: %s", layout_name.c_str());
955 return result.dump();
956}
957
958std::string WasmControlApi::GetAvailableLayouts() {
959 nlohmann::json result = nlohmann::json::array();
960
961 // Built-in layouts
962 result.push_back("overworld_default");
963 result.push_back("dungeon_default");
964 result.push_back("graphics_default");
965 result.push_back("debug_default");
966 result.push_back("minimal");
967 result.push_back("all_cards");
968
969 return result.dump();
970}
971
972std::string WasmControlApi::SaveCurrentLayout(const std::string& layout_name) {
973 nlohmann::json result;
974
975 if (!IsReady()) {
976 result["success"] = false;
977 result["error"] = "Control API not initialized";
978 return result.dump();
979 }
980
981 // TODO: Save to workspace presets
982 result["success"] = true;
983 result["layout"] = layout_name;
984
985 return result.dump();
986}
987
988// ============================================================================
989// Menu/UI Actions Implementation
990// ============================================================================
991
992std::string WasmControlApi::TriggerMenuAction(const std::string& action_path) {
993 nlohmann::json result;
994
995 if (!IsReady()) {
996 result["success"] = false;
997 result["error"] = "Control API not initialized";
998 return result.dump();
999 }
1000
1001 // Parse action path (e.g., "File.Save", "Edit.Undo")
1002 // TODO: Map to actual menu callbacks
1003
1004 if (action_path == "File.Save") {
1005 auto status = editor_manager_->SaveRom();
1006 result["success"] = status.ok();
1007 if (!status.ok()) {
1008 result["error"] = status.ToString();
1009 }
1010 } else if (action_path == "View.ShowEmulator") {
1011 editor_manager_->ui_coordinator()->SetEmulatorVisible(true);
1012 result["success"] = true;
1013 } else if (action_path == "View.ShowWelcome") {
1014 editor_manager_->ui_coordinator()->SetWelcomeScreenVisible(true);
1015 result["success"] = true;
1016 } else {
1017 result["success"] = false;
1018 result["error"] = "Unknown action: " + action_path;
1019 }
1020
1021 return result.dump();
1022}
1023
1024std::string WasmControlApi::GetAvailableMenuActions() {
1025 nlohmann::json result = nlohmann::json::array();
1026
1027 // File menu
1028 result.push_back("File.Open");
1029 result.push_back("File.Save");
1030 result.push_back("File.SaveAs");
1031 result.push_back("File.NewProject");
1032 result.push_back("File.OpenProject");
1033
1034 // Edit menu
1035 result.push_back("Edit.Undo");
1036 result.push_back("Edit.Redo");
1037 result.push_back("Edit.Cut");
1038 result.push_back("Edit.Copy");
1039 result.push_back("Edit.Paste");
1040
1041 // View menu
1042 result.push_back("View.ShowEmulator");
1043 result.push_back("View.ShowWelcome");
1044 result.push_back("View.ShowPanelBrowser");
1045 result.push_back("View.ShowMemoryEditor");
1046 result.push_back("View.ShowHexEditor");
1047
1048 // Tools menu
1049 result.push_back("Tools.GlobalSearch");
1050 result.push_back("Tools.CommandPalette");
1051
1052 return result.dump();
1053}
1054
1055// ============================================================================
1056// Session Control Implementation
1057// ============================================================================
1058
1059std::string WasmControlApi::GetSessionInfo() {
1060 nlohmann::json result;
1061
1062 if (!IsReady()) {
1063 result["error"] = "Control API not initialized";
1064 return result.dump();
1065 }
1066
1067 result["session_index"] = editor_manager_->GetCurrentSessionIndex();
1068 result["session_count"] = editor_manager_->GetActiveSessionCount();
1069
1070 auto* rom = editor_manager_->GetCurrentRom();
1071 if (rom && rom->is_loaded()) {
1072 result["rom_loaded"] = true;
1073 result["rom_filename"] = rom->filename();
1074 result["rom_title"] = rom->title();
1075 } else {
1076 result["rom_loaded"] = false;
1077 }
1078
1079 auto* current_editor = editor_manager_->GetCurrentEditor();
1080 if (current_editor) {
1081 result["current_editor"] = EditorTypeToString(static_cast<int>(current_editor->type()));
1082 }
1083
1084 return result.dump();
1085}
1086
1087std::string WasmControlApi::CreateSession() {
1088 nlohmann::json result;
1089
1090 if (!IsReady()) {
1091 result["success"] = false;
1092 result["error"] = "Control API not initialized";
1093 return result.dump();
1094 }
1095
1096 editor_manager_->CreateNewSession();
1097 result["success"] = true;
1098 result["session_index"] = editor_manager_->GetCurrentSessionIndex();
1099
1100 return result.dump();
1101}
1102
1103std::string WasmControlApi::SwitchSession(int session_index) {
1104 nlohmann::json result;
1105
1106 if (!IsReady()) {
1107 result["success"] = false;
1108 result["error"] = "Control API not initialized";
1109 return result.dump();
1110 }
1111
1112 if (session_index < 0 || static_cast<size_t>(session_index) >= editor_manager_->GetActiveSessionCount()) {
1113 result["success"] = false;
1114 result["error"] = "Invalid session index";
1115 return result.dump();
1116 }
1117
1118 editor_manager_->SwitchToSession(static_cast<size_t>(session_index));
1119 result["success"] = true;
1120 result["session_index"] = session_index;
1121
1122 return result.dump();
1123}
1124
1125// ============================================================================
1126// ROM Control Implementation
1127// ============================================================================
1128
1129std::string WasmControlApi::GetRomStatus() {
1130 nlohmann::json result;
1131
1132 if (!IsReady()) {
1133 result["error"] = "Control API not initialized";
1134 return result.dump();
1135 }
1136
1137 auto* rom = editor_manager_->GetCurrentRom();
1138 if (rom && rom->is_loaded()) {
1139 result["loaded"] = true;
1140 result["filename"] = rom->filename();
1141 result["title"] = rom->title();
1142 result["size"] = rom->size();
1143 result["dirty"] = rom->dirty();
1144 } else {
1145 result["loaded"] = false;
1146 }
1147
1148 return result.dump();
1149}
1150
1151std::string WasmControlApi::ReadRomBytes(int address, int count) {
1152 nlohmann::json result;
1153
1154 if (!IsReady()) {
1155 result["error"] = "Control API not initialized";
1156 return result.dump();
1157 }
1158
1159 auto* rom = editor_manager_->GetCurrentRom();
1160 if (!rom || !rom->is_loaded()) {
1161 result["error"] = "No ROM loaded";
1162 return result.dump();
1163 }
1164
1165 // Limit read size
1166 count = std::min(count, 256);
1167
1168 if (address < 0 || static_cast<size_t>(address + count) > rom->size()) {
1169 result["error"] = "Address out of range";
1170 return result.dump();
1171 }
1172
1173 result["address"] = address;
1174 result["count"] = count;
1175
1176 nlohmann::json bytes = nlohmann::json::array();
1177 for (int i = 0; i < count; ++i) {
1178 auto byte_result = rom->ReadByte(address + i);
1179 if (byte_result.ok()) {
1180 bytes.push_back(*byte_result);
1181 } else {
1182 bytes.push_back(0);
1183 }
1184 }
1185 result["bytes"] = bytes;
1186
1187 return result.dump();
1188}
1189
1190std::string WasmControlApi::WriteRomBytes(int address, const std::string& bytes_json) {
1191 nlohmann::json result;
1192
1193 if (!IsReady()) {
1194 result["success"] = false;
1195 result["error"] = "Control API not initialized";
1196 return result.dump();
1197 }
1198
1199 auto* rom = editor_manager_->GetCurrentRom();
1200 if (!rom || !rom->is_loaded()) {
1201 result["success"] = false;
1202 result["error"] = "No ROM loaded";
1203 return result.dump();
1204 }
1205
1206 try {
1207 auto bytes = nlohmann::json::parse(bytes_json);
1208 if (!bytes.is_array()) {
1209 result["success"] = false;
1210 result["error"] = "Invalid bytes format - expected array";
1211 return result.dump();
1212 }
1213
1214 for (size_t i = 0; i < bytes.size(); ++i) {
1215 uint8_t value = bytes[i].get<uint8_t>();
1216 auto status = rom->WriteByte(address + static_cast<int>(i), value);
1217 if (!status.ok()) {
1218 result["success"] = false;
1219 result["error"] = status.ToString();
1220 return result.dump();
1221 }
1222 }
1223
1224 result["success"] = true;
1225 result["bytes_written"] = bytes.size();
1226
1227 } catch (const std::exception& e) {
1228 result["success"] = false;
1229 result["error"] = e.what();
1230 }
1231
1232 return result.dump();
1233}
1234
1235std::string WasmControlApi::SaveRom() {
1236 nlohmann::json result;
1237
1238 if (!IsReady()) {
1239 result["success"] = false;
1240 result["error"] = "Control API not initialized";
1241 return result.dump();
1242 }
1243
1244 auto status = editor_manager_->SaveRom();
1245 result["success"] = status.ok();
1246 if (!status.ok()) {
1247 result["error"] = status.ToString();
1248 }
1249
1250 return result.dump();
1251}
1252
1253// ============================================================================
1254// Editor State APIs Implementation
1255// ============================================================================
1256
1257std::string WasmControlApi::GetEditorSnapshot() {
1258 nlohmann::json result;
1259
1260 if (!IsReady()) {
1261 result["error"] = "Control API not initialized";
1262 return result.dump();
1263 }
1264
1265 auto* current = editor_manager_->GetCurrentEditor();
1266 if (!current) {
1267 result["editor_type"] = "none";
1268 result["active"] = false;
1269 return result.dump();
1270 }
1271
1272 result["editor_type"] = EditorTypeToString(static_cast<int>(current->type()));
1273 result["editor_type_id"] = static_cast<int>(current->type());
1274 result["active"] = *current->active();
1275
1276 // Add ROM status
1277 auto* rom = editor_manager_->GetCurrentRom();
1278 if (rom && rom->is_loaded()) {
1279 result["rom_loaded"] = true;
1280 result["rom_title"] = rom->title();
1281 } else {
1282 result["rom_loaded"] = false;
1283 }
1284
1285 // Add editor-specific data based on type
1286 nlohmann::json active_data;
1287 auto* editor_set = editor_manager_->GetCurrentEditorSet();
1288
1289 if (current->type() == editor::EditorType::kDungeon && editor_set) {
1290 auto* dungeon = editor_set->GetDungeonEditor();
1291 if (dungeon) {
1292 active_data["current_room_id"] = dungeon->current_room_id();
1293
1294 nlohmann::json active_rooms = nlohmann::json::array();
1295 for (int i = 0; i < dungeon->active_rooms().size(); ++i) {
1296 active_rooms.push_back(dungeon->active_rooms()[i]);
1297 }
1298 active_data["active_rooms"] = active_rooms;
1299 active_data["room_count"] = dungeon->active_rooms().size();
1300 }
1301
1302 } else if (current->type() == editor::EditorType::kOverworld && editor_set) {
1303 auto* overworld = editor_set->GetOverworldEditor();
1304 if (overworld) {
1305 active_data["current_map"] = overworld->overworld().current_map_id();
1306 active_data["current_world"] = overworld->overworld().current_world();
1307 active_data["map_count"] = zelda3::kNumOverworldMaps;
1308 }
1309 }
1310
1311 result["active_data"] = active_data;
1312
1313 return result.dump();
1314}
1315
1316std::string WasmControlApi::GetCurrentDungeonRoom() {
1317 nlohmann::json result;
1318
1319 if (!IsReady()) {
1320 result["error"] = "Control API not initialized";
1321 return result.dump();
1322 }
1323
1324 auto* current = editor_manager_->GetCurrentEditor();
1325 if (!current || current->type() != editor::EditorType::kDungeon) {
1326 result["error"] = "Dungeon editor not active";
1327 result["editor_type"] = current ? EditorTypeToString(static_cast<int>(current->type())) : "none";
1328 return result.dump();
1329 }
1330
1331 auto* editor_set = editor_manager_->GetCurrentEditorSet();
1332 if (!editor_set) {
1333 result["error"] = "No editor set available";
1334 return result.dump();
1335 }
1336
1337 auto* dungeon = editor_set->GetDungeonEditor();
1338 if (!dungeon) {
1339 result["error"] = "Dungeon editor not available";
1340 return result.dump();
1341 }
1342 result["room_id"] = dungeon->current_room_id();
1343
1344 // Get active rooms list
1345 nlohmann::json active_rooms = nlohmann::json::array();
1346 for (int i = 0; i < dungeon->active_rooms().size(); ++i) {
1347 active_rooms.push_back(dungeon->active_rooms()[i]);
1348 }
1349 result["active_rooms"] = active_rooms;
1350 result["room_count"] = dungeon->active_rooms().size();
1351
1352 // Panel visibility state
1353 nlohmann::json cards;
1354 // TODO: Fix editor visibility controls
1355 // cards["room_selector"] = dungeon->show_room_selector_;
1356 // cards["room_matrix"] = dungeon->show_room_matrix_;
1357 // cards["entrances_list"] = dungeon->show_entrances_list_;
1358 // cards["room_graphics"] = dungeon->show_room_graphics_;
1359 // cards["object_editor"] = dungeon->show_object_editor_;
1360 // cards["palette_editor"] = dungeon->show_palette_editor_;
1361 // cards["debug_controls"] = dungeon->show_debug_controls_;
1362 // cards["control_panel"] = dungeon->show_control_panel_;
1363 result["visible_cards"] = cards;
1364
1365 return result.dump();
1366}
1367
1368std::string WasmControlApi::GetCurrentOverworldMap() {
1369 nlohmann::json result;
1370
1371 if (!IsReady()) {
1372 result["error"] = "Control API not initialized";
1373 return result.dump();
1374 }
1375
1376 auto* current = editor_manager_->GetCurrentEditor();
1377 if (!current || current->type() != editor::EditorType::kOverworld) {
1378 result["error"] = "Overworld editor not active";
1379 result["editor_type"] = current ? EditorTypeToString(static_cast<int>(current->type())) : "none";
1380 return result.dump();
1381 }
1382
1383 auto* editor_set = editor_manager_->GetCurrentEditorSet();
1384 if (!editor_set) {
1385 result["error"] = "No editor set available";
1386 return result.dump();
1387 }
1388
1389 auto* overworld = editor_set->GetOverworldEditor();
1390 if (!overworld) {
1391 result["error"] = "Overworld editor not available";
1392 return result.dump();
1393 }
1394 auto& ow_data = overworld->overworld();
1395
1396 result["map_id"] = ow_data.current_map_id();
1397 result["world"] = ow_data.current_world();
1398 result["world_name"] = ow_data.current_world() == 0 ? "Light World" :
1399 (ow_data.current_world() == 1 ? "Dark World" : "Special World");
1400 result["map_count"] = zelda3::kNumOverworldMaps;
1401
1402 return result.dump();
1403}
1404
1405std::string WasmControlApi::GetEditorSelection() {
1406 nlohmann::json result;
1407
1408 if (!IsReady()) {
1409 result["error"] = "Control API not initialized";
1410 return result.dump();
1411 }
1412
1413 auto* current = editor_manager_->GetCurrentEditor();
1414 if (!current) {
1415 result["error"] = "No editor active";
1416 return result.dump();
1417 }
1418
1419 result["editor_type"] = EditorTypeToString(static_cast<int>(current->type()));
1420 result["selection"] = nlohmann::json::array(); // Placeholder for future selection data
1421
1422 // TODO: Implement editor-specific selection queries
1423 // For now, return empty selection
1424 result["has_selection"] = false;
1425
1426 return result.dump();
1427}
1428
1429// ============================================================================
1430// Read-only Data APIs Implementation
1431// ============================================================================
1432
1433std::string WasmControlApi::GetRoomTileData(int room_id) {
1434 nlohmann::json result;
1435
1436 if (!IsReady()) {
1437 result["error"] = "Control API not initialized";
1438 return result.dump();
1439 }
1440
1441 if (room_id < 0 || room_id >= 296) {
1442 result["error"] = "Invalid room ID (must be 0-295)";
1443 return result.dump();
1444 }
1445
1446 auto* rom = editor_manager_->GetCurrentRom();
1447 if (!rom || !rom->is_loaded()) {
1448 result["error"] = "ROM not loaded";
1449 return result.dump();
1450 }
1451
1452 // Load room from ROM
1453 zelda3::Room room = zelda3::LoadRoomFromRom(rom, room_id);
1454 auto* game_data = editor_manager_->GetCurrentGameData();
1455 if (game_data) {
1456 room.SetGameData(game_data); // Ensure room has access to GameData
1457 }
1458 room.LoadRoomGraphics();
1459 room.LoadObjects();
1460
1461 result["room_id"] = room_id;
1462 result["width"] = 512;
1463 result["height"] = 512;
1464
1465 // Get layout objects for both layers
1466 const auto& layout = room.GetLayout();
1467 const auto& layout_objects = layout.GetObjects();
1468
1469 // Extract tile data for layer 1 and layer 2
1470 nlohmann::json layer1_tiles = nlohmann::json::array();
1471 nlohmann::json layer2_tiles = nlohmann::json::array();
1472
1473 for (const auto& obj : layout_objects) {
1474 nlohmann::json tile_obj;
1475 tile_obj["x"] = obj.x();
1476 tile_obj["y"] = obj.y();
1477
1478 auto tile_result = obj.GetTile(0);
1479 if (tile_result.ok()) {
1480 const auto* tile_info = tile_result.value();
1481 tile_obj["tile_id"] = tile_info->id_;
1482 tile_obj["palette"] = tile_info->palette_;
1483 tile_obj["priority"] = tile_info->over_;
1484 tile_obj["h_flip"] = tile_info->horizontal_mirror_;
1485 tile_obj["v_flip"] = tile_info->vertical_mirror_;
1486
1487 if (obj.GetLayerValue() == 1) {
1488 layer2_tiles.push_back(tile_obj);
1489 } else {
1490 layer1_tiles.push_back(tile_obj);
1491 }
1492 }
1493 }
1494
1495 result["layer1"] = layer1_tiles;
1496 result["layer2"] = layer2_tiles;
1497 result["layer1_count"] = layer1_tiles.size();
1498 result["layer2_count"] = layer2_tiles.size();
1499
1500 return result.dump();
1501}
1502
1503std::string WasmControlApi::GetRoomObjects(int room_id) {
1504 nlohmann::json result = nlohmann::json::array();
1505
1506 if (!IsReady()) {
1507 nlohmann::json error;
1508 error["error"] = "Control API not initialized";
1509 return error.dump();
1510 }
1511
1512 if (room_id < 0 || room_id >= 296) {
1513 nlohmann::json error;
1514 error["error"] = "Invalid room ID (must be 0-295)";
1515 return error.dump();
1516 }
1517
1518 auto* rom = editor_manager_->GetCurrentRom();
1519 if (!rom || !rom->is_loaded()) {
1520 nlohmann::json error;
1521 error["error"] = "ROM not loaded";
1522 return error.dump();
1523 }
1524
1525 // Load room from ROM
1526 zelda3::Room room = zelda3::LoadRoomFromRom(rom, room_id);
1527 room.LoadObjects();
1528
1529 // Get tile objects from the room
1530 const auto& tile_objects = room.GetTileObjects();
1531
1532 for (const auto& obj : tile_objects) {
1533 nlohmann::json obj_data;
1534 obj_data["id"] = obj.id_;
1535 obj_data["x"] = obj.x();
1536 obj_data["y"] = obj.y();
1537 obj_data["size"] = obj.size();
1538 obj_data["layer"] = obj.GetLayerValue();
1539
1540 // Add object type information
1541 auto options = static_cast<int>(obj.options());
1542 obj_data["is_door"] = (options & static_cast<int>(zelda3::ObjectOption::Door)) != 0;
1543 obj_data["is_chest"] = (options & static_cast<int>(zelda3::ObjectOption::Chest)) != 0;
1544 obj_data["is_block"] = (options & static_cast<int>(zelda3::ObjectOption::Block)) != 0;
1545 obj_data["is_torch"] = (options & static_cast<int>(zelda3::ObjectOption::Torch)) != 0;
1546 obj_data["is_stairs"] = (options & static_cast<int>(zelda3::ObjectOption::Stairs)) != 0;
1547
1548 result.push_back(obj_data);
1549 }
1550
1551 return result.dump();
1552}
1553
1554std::string WasmControlApi::GetRoomProperties(int room_id) {
1555 nlohmann::json result;
1556
1557 if (!IsReady()) {
1558 result["error"] = "Control API not initialized";
1559 return result.dump();
1560 }
1561
1562 if (room_id < 0 || room_id >= 296) {
1563 result["error"] = "Invalid room ID (must be 0-295)";
1564 return result.dump();
1565 }
1566
1567 auto* rom = editor_manager_->GetCurrentRom();
1568 if (!rom || !rom->is_loaded()) {
1569 result["error"] = "ROM not loaded";
1570 return result.dump();
1571 }
1572
1573 // Load room from ROM
1574 zelda3::Room room = zelda3::LoadRoomFromRom(rom, room_id);
1575
1576 result["room_id"] = room_id;
1577 result["blockset"] = room.blockset;
1578 result["spriteset"] = room.spriteset;
1579 result["palette"] = room.palette;
1580 result["floor1"] = room.floor1();
1581 result["floor2"] = room.floor2();
1582 result["layout"] = room.layout;
1583 result["holewarp"] = room.holewarp;
1584 result["message_id"] = room.message_id_;
1585
1586 // Effect and tags
1587 result["effect"] = static_cast<int>(room.effect());
1588 result["tag1"] = static_cast<int>(room.tag1());
1589 result["tag2"] = static_cast<int>(room.tag2());
1590 result["collision"] = static_cast<int>(room.collision());
1591
1592 // Layer merging info
1593 const auto& layer_merge = room.layer_merging();
1594 result["layer_merging"] = {
1595 {"id", layer_merge.ID},
1596 {"name", layer_merge.Name},
1597 {"layer2_visible", layer_merge.Layer2Visible},
1598 {"layer2_on_top", layer_merge.Layer2OnTop},
1599 {"layer2_translucent", layer_merge.Layer2Translucent}
1600 };
1601
1602 result["is_light"] = room.IsLight();
1603 result["is_loaded"] = room.IsLoaded();
1604
1605 return result.dump();
1606}
1607
1608std::string WasmControlApi::GetMapTileData(int map_id) {
1609 nlohmann::json result;
1610
1611 if (!IsReady()) {
1612 result["error"] = "Control API not initialized";
1613 return result.dump();
1614 }
1615
1616 if (map_id < 0 || map_id >= static_cast<int>(zelda3::kNumOverworldMaps)) {
1617 result["error"] = "Invalid map ID (must be 0-159)";
1618 return result.dump();
1619 }
1620
1621 auto* overworld = editor_manager_->overworld();
1622 if (!overworld) {
1623 result["error"] = "Overworld not loaded";
1624 return result.dump();
1625 }
1626
1627 auto* map = overworld->overworld_map(map_id);
1628 if (!map) {
1629 result["error"] = "Map not found";
1630 return result.dump();
1631 }
1632
1633 result["map_id"] = map_id;
1634 result["width"] = 32;
1635 result["height"] = 32;
1636
1637 // Get tile blockset data (this is the 32x32 tile16 data for the map)
1638 auto blockset = map->current_tile16_blockset();
1639
1640 // Instead of dumping all 1024 tiles, provide summary information
1641 result["has_tile_data"] = !blockset.empty();
1642 result["tile_count"] = blockset.size();
1643 result["is_built"] = map->is_built();
1644 result["is_large_map"] = map->is_large_map();
1645
1646 // Note: Full tile extraction would be very large (1024 tiles)
1647 // Only extract a small sample or provide it on request
1648 if (blockset.size() >= 64) {
1649 nlohmann::json sample_tiles = nlohmann::json::array();
1650 // Extract first 8x8 corner as a sample
1651 for (int i = 0; i < 64; i++) {
1652 sample_tiles.push_back(static_cast<int>(blockset[i]));
1653 }
1654 result["sample_tiles"] = sample_tiles;
1655 result["sample_note"] = "First 8x8 tiles from top-left corner";
1656 }
1657
1658 return result.dump();
1659}
1660
1661std::string WasmControlApi::GetMapEntities(int map_id) {
1662 nlohmann::json result;
1663
1664 if (!IsReady()) {
1665 result["error"] = "Control API not initialized";
1666 return result.dump();
1667 }
1668
1669 if (map_id < 0 || map_id >= static_cast<int>(zelda3::kNumOverworldMaps)) {
1670 result["error"] = "Invalid map ID (must be 0-159)";
1671 return result.dump();
1672 }
1673
1674 auto* overworld = editor_manager_->overworld();
1675 if (!overworld) {
1676 result["error"] = "Overworld not loaded";
1677 return result.dump();
1678 }
1679
1680 result["map_id"] = map_id;
1681 result["entrances"] = nlohmann::json::array();
1682 result["exits"] = nlohmann::json::array();
1683 result["items"] = nlohmann::json::array();
1684 result["sprites"] = nlohmann::json::array();
1685
1686 // Get entrances for this map
1687 for (const auto& entrance : overworld->entrances()) {
1688 if (entrance.map_id_ == static_cast<uint16_t>(map_id)) {
1689 nlohmann::json e;
1690 e["id"] = entrance.entrance_id_;
1691 e["x"] = entrance.x_;
1692 e["y"] = entrance.y_;
1693 e["map_id"] = entrance.map_id_;
1694 result["entrances"].push_back(e);
1695 }
1696 }
1697
1698 // Get exits for this map
1699 auto* exits = overworld->exits();
1700 if (exits) {
1701 for (const auto& exit : *exits) {
1702 if (exit.map_id_ == static_cast<uint16_t>(map_id)) {
1703 nlohmann::json ex;
1704 ex["x"] = exit.x_;
1705 ex["y"] = exit.y_;
1706 ex["map_id"] = exit.map_id_;
1707 ex["room_id"] = exit.room_id_;
1708 result["exits"].push_back(ex);
1709 }
1710 }
1711 }
1712
1713 // Get items for this map (using map_id_ from GameEntity base class)
1714 for (const auto& item : overworld->all_items()) {
1715 if (item.map_id_ == static_cast<uint16_t>(map_id)) {
1716 nlohmann::json i;
1717 i["id"] = item.id_;
1718 i["x"] = item.x_;
1719 i["y"] = item.y_;
1720 result["items"].push_back(i);
1721 }
1722 }
1723
1724 return result.dump();
1725}
1726
1727std::string WasmControlApi::GetMapProperties(int map_id) {
1728 nlohmann::json result;
1729
1730 if (!IsReady()) {
1731 result["error"] = "Control API not initialized";
1732 return result.dump();
1733 }
1734
1735 if (map_id < 0 || map_id >= static_cast<int>(zelda3::kNumOverworldMaps)) {
1736 result["error"] = "Invalid map ID (must be 0-159)";
1737 return result.dump();
1738 }
1739
1740 auto* overworld = editor_manager_->overworld();
1741 if (!overworld) {
1742 result["error"] = "Overworld not loaded";
1743 return result.dump();
1744 }
1745
1746 auto* map = overworld->overworld_map(map_id);
1747 if (!map) {
1748 result["error"] = "Map not found";
1749 return result.dump();
1750 }
1751
1752 result["map_id"] = map_id;
1753 result["world"] = map_id / 64;
1754 result["parent_id"] = map->parent();
1755 result["area_graphics"] = map->area_graphics();
1756 result["area_palette"] = map->area_palette();
1757 result["sprite_graphics"] = {map->sprite_graphics(0), map->sprite_graphics(1), map->sprite_graphics(2)};
1758 result["sprite_palette"] = {map->sprite_palette(0), map->sprite_palette(1), map->sprite_palette(2)};
1759 result["message_id"] = map->message_id();
1760 result["is_large_map"] = map->is_large_map();
1761
1762 return result.dump();
1763}
1764
1765std::string WasmControlApi::GetPaletteData(const std::string& group_name, int palette_id) {
1766 nlohmann::json result;
1767
1768 if (!IsReady()) {
1769 result["error"] = "Control API not initialized";
1770 return result.dump();
1771 }
1772
1773 auto* rom = editor_manager_->GetCurrentRom();
1774 if (!rom || !rom->is_loaded()) {
1775 result["error"] = "ROM not loaded";
1776 return result.dump();
1777 }
1778
1779 result["group"] = group_name;
1780 result["palette_id"] = palette_id;
1781
1782 try {
1783 auto* game_data = editor_manager_->GetCurrentGameData();
1784 if (!game_data) {
1785 result["error"] = "GameData not available";
1786 return result.dump();
1787 }
1788 auto* group = game_data->palette_groups.get_group(group_name);
1789
1790 if (!group) {
1791 result["error"] = "Invalid palette group name";
1792 return result.dump();
1793 }
1794
1795 if (palette_id < 0 || palette_id >= static_cast<int>(group->size())) {
1796 result["error"] = "Invalid palette ID for this group";
1797 result["max_palette_id"] = group->size() - 1;
1798 return result.dump();
1799 }
1800
1801 auto palette = (*group)[palette_id];
1802 nlohmann::json colors = nlohmann::json::array();
1803
1804 // Extract color values
1805 for (size_t i = 0; i < palette.size(); i++) {
1806 const auto& color = palette[i];
1807 nlohmann::json color_data;
1808 color_data["index"] = i;
1809
1810 // Convert SNES color to RGB
1811 auto snes_color = color.snes();
1812 auto rgb_color = color.rgb();
1813
1814 // ImVec4 uses x,y,z,w for r,g,b,a in 0.0-1.0 range
1815 int r = static_cast<int>(rgb_color.x * 255);
1816 int g = static_cast<int>(rgb_color.y * 255);
1817 int b = static_cast<int>(rgb_color.z * 255);
1818 color_data["r"] = r;
1819 color_data["g"] = g;
1820 color_data["b"] = b;
1821 color_data["hex"] = absl::StrFormat("#%02X%02X%02X", r, g, b);
1822 color_data["snes_value"] = snes_color;
1823
1824 colors.push_back(color_data);
1825 }
1826
1827 result["colors"] = colors;
1828 result["color_count"] = palette.size();
1829
1830 } catch (const std::exception& e) {
1831 result["error"] = std::string("Failed to extract palette: ") + e.what();
1832 }
1833
1834 return result.dump();
1835}
1836
1837std::string WasmControlApi::ListPaletteGroups() {
1838 nlohmann::json result = nlohmann::json::array();
1839
1840 // List available palette groups (matching PaletteGroupMap structure)
1841 result.push_back("ow_main");
1842 result.push_back("ow_aux");
1843 result.push_back("ow_animated");
1844 result.push_back("hud");
1845 result.push_back("global_sprites");
1846 result.push_back("armors");
1847 result.push_back("swords");
1848 result.push_back("shields");
1849 result.push_back("sprites_aux1");
1850 result.push_back("sprites_aux2");
1851 result.push_back("sprites_aux3");
1852 result.push_back("dungeon_main");
1853 result.push_back("grass");
1854 result.push_back("3d_object");
1855 result.push_back("ow_mini_map");
1856
1857 return result.dump();
1858}
1859
1860std::string WasmControlApi::LoadFont(const std::string& name, const std::string& data, float size) {
1861 nlohmann::json result;
1862 auto status = yaze::platform::WasmSettings::LoadUserFont(name, data, size);
1863 if (status.ok()) {
1864 result["success"] = true;
1865 } else {
1866 result["success"] = false;
1867 result["error"] = status.ToString();
1868 }
1869 return result.dump();
1870}
1871
1872// ============================================================================
1873// GUI Automation APIs Implementation
1874// ============================================================================
1875
1876std::string WasmControlApi::GetUIElementTree() {
1877 nlohmann::json result;
1878
1879 if (!IsReady()) {
1880 result["error"] = "Control API not initialized";
1881 result["elements"] = nlohmann::json::array();
1882 return result.dump();
1883 }
1884
1885 // Query the WidgetIdRegistry for all registered widgets
1886 auto& registry = gui::WidgetIdRegistry::Instance();
1887 const auto& all_widgets = registry.GetAllWidgets();
1888
1889 nlohmann::json elements = nlohmann::json::array();
1890
1891 // Convert WidgetInfo to JSON elements
1892 for (const auto& [path, info] : all_widgets) {
1893 nlohmann::json elem;
1894 elem["id"] = info.full_path;
1895 elem["type"] = info.type;
1896 elem["label"] = info.label;
1897 elem["enabled"] = info.enabled;
1898 elem["visible"] = info.visible;
1899 elem["window"] = info.window_name;
1900
1901 // Add bounds if available
1902 if (info.bounds.valid) {
1903 elem["bounds"] = {
1904 {"x", info.bounds.min_x},
1905 {"y", info.bounds.min_y},
1906 {"width", info.bounds.max_x - info.bounds.min_x},
1907 {"height", info.bounds.max_y - info.bounds.min_y}
1908 };
1909 } else {
1910 elem["bounds"] = {
1911 {"x", 0}, {"y", 0}, {"width", 0}, {"height", 0}
1912 };
1913 }
1914
1915 // Add metadata
1916 if (!info.description.empty()) {
1917 elem["description"] = info.description;
1918 }
1919 elem["imgui_id"] = static_cast<uint32_t>(info.imgui_id);
1920 elem["last_seen_frame"] = info.last_seen_frame;
1921
1922 elements.push_back(elem);
1923 }
1924
1925 result["elements"] = elements;
1926 result["count"] = elements.size();
1927 result["source"] = "WidgetIdRegistry";
1928
1929 return result.dump();
1930}
1931
1932std::string WasmControlApi::GetUIElementBounds(const std::string& element_id) {
1933 nlohmann::json result;
1934
1935 if (!IsReady()) {
1936 result["error"] = "Control API not initialized";
1937 return result.dump();
1938 }
1939
1940 // Query the WidgetIdRegistry for the specific widget
1941 auto& registry = gui::WidgetIdRegistry::Instance();
1942 const auto* widget_info = registry.GetWidgetInfo(element_id);
1943
1944 result["id"] = element_id;
1945
1946 if (widget_info == nullptr) {
1947 result["found"] = false;
1948 result["error"] = "Element not found: " + element_id;
1949 return result.dump();
1950 }
1951
1952 result["found"] = true;
1953 result["visible"] = widget_info->visible;
1954 result["enabled"] = widget_info->enabled;
1955 result["type"] = widget_info->type;
1956 result["label"] = widget_info->label;
1957 result["window"] = widget_info->window_name;
1958
1959 // Add bounds if available
1960 if (widget_info->bounds.valid) {
1961 result["x"] = widget_info->bounds.min_x;
1962 result["y"] = widget_info->bounds.min_y;
1963 result["width"] = widget_info->bounds.max_x - widget_info->bounds.min_x;
1964 result["height"] = widget_info->bounds.max_y - widget_info->bounds.min_y;
1965 result["bounds_valid"] = true;
1966 } else {
1967 result["x"] = 0;
1968 result["y"] = 0;
1969 result["width"] = 0;
1970 result["height"] = 0;
1971 result["bounds_valid"] = false;
1972 }
1973
1974 // Add metadata
1975 result["imgui_id"] = static_cast<uint32_t>(widget_info->imgui_id);
1976 result["last_seen_frame"] = widget_info->last_seen_frame;
1977
1978 if (!widget_info->description.empty()) {
1979 result["description"] = widget_info->description;
1980 }
1981
1982 return result.dump();
1983}
1984
1985std::string WasmControlApi::SetSelection(const std::string& ids_json) {
1986 nlohmann::json result;
1987
1988 if (!IsReady()) {
1989 result["success"] = false;
1990 result["error"] = "Control API not initialized";
1991 return result.dump();
1992 }
1993
1994 try {
1995 auto ids = nlohmann::json::parse(ids_json);
1996
1997 // TODO: Implement actual selection setting based on active editor
1998 // For now, return success with the IDs that would be selected
1999 result["success"] = true;
2000 result["selected_ids"] = ids;
2001 result["note"] = "Selection setting not yet fully implemented";
2002
2003 } catch (const std::exception& e) {
2004 result["success"] = false;
2005 result["error"] = std::string("Invalid JSON: ") + e.what();
2006 }
2007
2008 return result.dump();
2009}
2010
2011// ============================================================================
2012// Platform Info API Implementation
2013// ============================================================================
2014
2015std::string WasmControlApi::GetPlatformInfo() {
2016 nlohmann::json result;
2017
2018 // Get current platform from runtime detection
2019 auto platform = gui::GetCurrentPlatform();
2020
2021 // Convert platform enum to string
2022 switch (platform) {
2024 result["platform"] = "Windows";
2025 break;
2027 result["platform"] = "macOS";
2028 break;
2030 result["platform"] = "Linux";
2031 break;
2033 result["platform"] = "WebMac";
2034 break;
2036 result["platform"] = "WebOther";
2037 break;
2038 default:
2039 result["platform"] = "Unknown";
2040 break;
2041 }
2042
2043 // Get platform-specific display names for modifiers
2044 result["is_mac"] = gui::IsMacPlatform();
2045 result["ctrl_display"] = gui::GetCtrlDisplayName();
2046 result["alt_display"] = gui::GetAltDisplayName();
2047 result["shift_display"] = "Shift";
2048
2049 // Example shortcut formatting
2050 result["example_save"] = gui::FormatCtrlShortcut(ImGuiKey_S);
2051 result["example_open"] = gui::FormatCtrlShortcut(ImGuiKey_O);
2052 result["example_command_palette"] = gui::FormatCtrlShiftShortcut(ImGuiKey_P);
2053
2054 return result.dump();
2055}
2056
2057// ============================================================================
2058// Agent API Implementations
2059// ============================================================================
2060
2061bool WasmControlApi::AgentIsReady() {
2062 if (!initialized_ || !editor_manager_) {
2063 return false;
2064 }
2065 // Check if agent editor is available
2066 auto* agent_editor = editor_manager_->GetAgentEditor();
2067 return agent_editor != nullptr;
2068}
2069
2070std::string WasmControlApi::AgentSendMessage(const std::string& message) {
2071 nlohmann::json result;
2072
2073 if (!initialized_ || !editor_manager_) {
2074 result["success"] = false;
2075 result["error"] = "API not initialized";
2076 return result.dump();
2077 }
2078
2079 auto* agent_editor = editor_manager_->GetAgentEditor();
2080 if (!agent_editor) {
2081 result["success"] = false;
2082 result["error"] = "Agent editor not available";
2083 return result.dump();
2084 }
2085
2086 auto* agent_chat = agent_editor->GetAgentChat();
2087 if (!agent_chat) {
2088 result["success"] = false;
2089 result["error"] = "Agent chat not available";
2090 return result.dump();
2091 }
2092
2093 // Queue the message for the agent
2094 // The actual processing happens asynchronously
2095 result["success"] = true;
2096 result["status"] = "queued";
2097 result["message"] = message;
2098
2099 // Note: Actual message sending will be handled by the agent chat
2100 // This API provides the interface for web-based agents to interact
2101
2102 return result.dump();
2103}
2104
2105std::string WasmControlApi::AgentGetChatHistory() {
2106 nlohmann::json result = nlohmann::json::array();
2107
2108 if (!initialized_ || !editor_manager_) {
2109 return result.dump();
2110 }
2111
2112 auto* agent_editor = editor_manager_->GetAgentEditor();
2113 if (!agent_editor) {
2114 return result.dump();
2115 }
2116
2117 auto* agent_chat = agent_editor->GetAgentChat();
2118 if (!agent_chat) {
2119 return result.dump();
2120 }
2121
2122 // Get chat history from the agent chat
2123 // For now, return empty array - full implementation requires
2124 // AgentChat to expose history via a public method
2125
2126 return result.dump();
2127}
2128
2129std::string WasmControlApi::AgentGetConfig() {
2130 nlohmann::json result;
2131
2132 if (!initialized_ || !editor_manager_) {
2133 result["error"] = "API not initialized";
2134 return result.dump();
2135 }
2136
2137 auto* agent_editor = editor_manager_->GetAgentEditor();
2138 if (!agent_editor) {
2139 result["error"] = "Agent editor not available";
2140 return result.dump();
2141 }
2142
2143 auto config = agent_editor->GetCurrentConfig();
2144 result["provider"] = config.provider;
2145 result["model"] = config.model;
2146 result["ollama_host"] = config.ollama_host;
2147 result["verbose"] = config.verbose;
2148 result["show_reasoning"] = config.show_reasoning;
2149 result["max_tool_iterations"] = config.max_tool_iterations;
2150
2151 return result.dump();
2152}
2153
2154std::string WasmControlApi::AgentSetConfig(const std::string& config_json) {
2155 nlohmann::json result;
2156
2157 if (!initialized_ || !editor_manager_) {
2158 result["success"] = false;
2159 result["error"] = "API not initialized";
2160 return result.dump();
2161 }
2162
2163 auto* agent_editor = editor_manager_->GetAgentEditor();
2164 if (!agent_editor) {
2165 result["success"] = false;
2166 result["error"] = "Agent editor not available";
2167 return result.dump();
2168 }
2169
2170 try {
2171 auto config_data = nlohmann::json::parse(config_json);
2172
2173 editor::AgentEditor::AgentConfig config;
2174 if (config_data.contains("provider")) {
2175 config.provider = config_data["provider"].get<std::string>();
2176 }
2177 if (config_data.contains("model")) {
2178 config.model = config_data["model"].get<std::string>();
2179 }
2180 if (config_data.contains("ollama_host")) {
2181 config.ollama_host = config_data["ollama_host"].get<std::string>();
2182 }
2183 if (config_data.contains("verbose")) {
2184 config.verbose = config_data["verbose"].get<bool>();
2185 }
2186 if (config_data.contains("show_reasoning")) {
2187 config.show_reasoning = config_data["show_reasoning"].get<bool>();
2188 }
2189 if (config_data.contains("max_tool_iterations")) {
2190 config.max_tool_iterations = config_data["max_tool_iterations"].get<int>();
2191 }
2192
2193 agent_editor->ApplyConfig(config);
2194 result["success"] = true;
2195 } catch (const std::exception& e) {
2196 result["success"] = false;
2197 result["error"] = e.what();
2198 }
2199
2200 return result.dump();
2201}
2202
2203std::string WasmControlApi::AgentGetProviders() {
2204 nlohmann::json result = nlohmann::json::array();
2205
2206 // List available AI providers
2207 result.push_back({
2208 {"id", "mock"},
2209 {"name", "Mock Provider"},
2210 {"description", "Testing provider that echoes messages"}
2211 });
2212 result.push_back({
2213 {"id", "ollama"},
2214 {"name", "Ollama"},
2215 {"description", "Local Ollama server"},
2216 {"requires_host", true}
2217 });
2218 result.push_back({
2219 {"id", "gemini"},
2220 {"name", "Google Gemini"},
2221 {"description", "Google's Gemini API"},
2222 {"requires_api_key", true}
2223 });
2224
2225 return result.dump();
2226}
2227
2228std::string WasmControlApi::AgentGetProposals() {
2229 nlohmann::json result = nlohmann::json::array();
2230
2231 if (!initialized_ || !editor_manager_) {
2232 return result.dump();
2233 }
2234
2235 // TODO: Integrate with proposal system when available
2236 // For now, return empty array
2237
2238 return result.dump();
2239}
2240
2241std::string WasmControlApi::AgentAcceptProposal(const std::string& proposal_id) {
2242 nlohmann::json result;
2243
2244 if (!initialized_ || !editor_manager_) {
2245 result["success"] = false;
2246 result["error"] = "API not initialized";
2247 return result.dump();
2248 }
2249
2250 // TODO: Integrate with proposal system when available
2251 result["success"] = false;
2252 result["error"] = "Proposal system not yet integrated";
2253 result["proposal_id"] = proposal_id;
2254
2255 return result.dump();
2256}
2257
2258std::string WasmControlApi::AgentRejectProposal(const std::string& proposal_id) {
2259 nlohmann::json result;
2260
2261 if (!initialized_ || !editor_manager_) {
2262 result["success"] = false;
2263 result["error"] = "API not initialized";
2264 return result.dump();
2265 }
2266
2267 // TODO: Integrate with proposal system when available
2268 result["success"] = false;
2269 result["error"] = "Proposal system not yet integrated";
2270 result["proposal_id"] = proposal_id;
2271
2272 return result.dump();
2273}
2274
2275std::string WasmControlApi::AgentGetProposalDetails(const std::string& proposal_id) {
2276 nlohmann::json result;
2277
2278 if (!initialized_ || !editor_manager_) {
2279 result["error"] = "API not initialized";
2280 return result.dump();
2281 }
2282
2283 // TODO: Integrate with proposal system when available
2284 result["error"] = "Proposal system not yet integrated";
2285 result["proposal_id"] = proposal_id;
2286
2287 return result.dump();
2288}
2289
2290std::string WasmControlApi::AgentOpenSidebar() {
2291 nlohmann::json result;
2292
2293 if (!initialized_ || !editor_manager_) {
2294 result["success"] = false;
2295 result["error"] = "API not initialized";
2296 return result.dump();
2297 }
2298
2299 auto* agent_editor = editor_manager_->GetAgentEditor();
2300 if (!agent_editor) {
2301 result["success"] = false;
2302 result["error"] = "Agent editor not available";
2303 return result.dump();
2304 }
2305
2306 agent_editor->SetChatActive(true);
2307 result["success"] = true;
2308 result["sidebar_open"] = true;
2309
2310 return result.dump();
2311}
2312
2313std::string WasmControlApi::AgentCloseSidebar() {
2314 nlohmann::json result;
2315
2316 if (!initialized_ || !editor_manager_) {
2317 result["success"] = false;
2318 result["error"] = "API not initialized";
2319 return result.dump();
2320 }
2321
2322 auto* agent_editor = editor_manager_->GetAgentEditor();
2323 if (!agent_editor) {
2324 result["success"] = false;
2325 result["error"] = "Agent editor not available";
2326 return result.dump();
2327 }
2328
2329 agent_editor->SetChatActive(false);
2330 result["success"] = true;
2331 result["sidebar_open"] = false;
2332
2333 return result.dump();
2334}
2335
2336// ============================================================================
2337// Emscripten Bindings
2338// ============================================================================
2339
2340EMSCRIPTEN_BINDINGS(wasm_control_api) {
2341 emscripten::function("controlIsReady", &WasmControlApi::IsReady);
2342 emscripten::function("controlSwitchEditor", &WasmControlApi::SwitchEditor);
2343 emscripten::function("controlGetCurrentEditor", &WasmControlApi::GetCurrentEditor);
2344 emscripten::function("controlGetAvailableEditors", &WasmControlApi::GetAvailableEditors);
2345 emscripten::function("controlOpenPanel", &WasmControlApi::OpenPanel);
2346 emscripten::function("controlClosePanel", &WasmControlApi::ClosePanel);
2347 emscripten::function("controlTogglePanel", &WasmControlApi::TogglePanel);
2348 emscripten::function("controlGetVisiblePanels", &WasmControlApi::GetVisiblePanels);
2349 emscripten::function("controlGetAvailablePanels", &WasmControlApi::GetAvailablePanels);
2350 emscripten::function("controlGetPanelsInCategory", &WasmControlApi::GetPanelsInCategory);
2351 emscripten::function("controlSetPanelLayout", &WasmControlApi::SetPanelLayout);
2352 emscripten::function("controlGetAvailableLayouts", &WasmControlApi::GetAvailableLayouts);
2353 emscripten::function("controlSaveCurrentLayout", &WasmControlApi::SaveCurrentLayout);
2354 emscripten::function("controlTriggerMenuAction", &WasmControlApi::TriggerMenuAction);
2355 emscripten::function("controlGetAvailableMenuActions", &WasmControlApi::GetAvailableMenuActions);
2356 emscripten::function("controlGetSessionInfo", &WasmControlApi::GetSessionInfo);
2357 emscripten::function("controlCreateSession", &WasmControlApi::CreateSession);
2358 emscripten::function("controlSwitchSession", &WasmControlApi::SwitchSession);
2359 emscripten::function("controlGetRomStatus", &WasmControlApi::GetRomStatus);
2360 emscripten::function("controlReadRomBytes", &WasmControlApi::ReadRomBytes);
2361 emscripten::function("controlWriteRomBytes", &WasmControlApi::WriteRomBytes);
2362 emscripten::function("controlSaveRom", &WasmControlApi::SaveRom);
2363
2364 // Editor State APIs
2365 emscripten::function("editorGetSnapshot", &WasmControlApi::GetEditorSnapshot);
2366 emscripten::function("editorGetCurrentDungeonRoom", &WasmControlApi::GetCurrentDungeonRoom);
2367 emscripten::function("editorGetCurrentOverworldMap", &WasmControlApi::GetCurrentOverworldMap);
2368 emscripten::function("editorGetSelection", &WasmControlApi::GetEditorSelection);
2369
2370 // Read-only Data APIs
2371 emscripten::function("dataGetRoomTileData", &WasmControlApi::GetRoomTileData);
2372 emscripten::function("dataGetRoomObjects", &WasmControlApi::GetRoomObjects);
2373 emscripten::function("dataGetRoomProperties", &WasmControlApi::GetRoomProperties);
2374 emscripten::function("dataGetMapTileData", &WasmControlApi::GetMapTileData);
2375 emscripten::function("dataGetMapEntities", &WasmControlApi::GetMapEntities);
2376 emscripten::function("dataGetMapProperties", &WasmControlApi::GetMapProperties);
2377 emscripten::function("dataGetPaletteData", &WasmControlApi::GetPaletteData);
2378 emscripten::function("dataListPaletteGroups", &WasmControlApi::ListPaletteGroups);
2379
2380 // GUI Automation APIs
2381 emscripten::function("guiGetUIElementTree", &WasmControlApi::GetUIElementTree);
2382 emscripten::function("guiGetUIElementBounds", &WasmControlApi::GetUIElementBounds);
2383 emscripten::function("guiSetSelection", &WasmControlApi::SetSelection);
2384
2385 // Settings APIs
2386 emscripten::function("settingsGetCurrentThemeData", &yaze::platform::WasmSettings::GetCurrentThemeData);
2387 emscripten::function("settingsLoadFont", &WasmControlApi::LoadFont);
2388
2389 // Platform Info API
2390 emscripten::function("controlGetPlatformInfo", &WasmControlApi::GetPlatformInfo);
2391
2392 // Agent API
2393 emscripten::function("agentIsReady", &WasmControlApi::AgentIsReady);
2394 emscripten::function("agentSendMessage", &WasmControlApi::AgentSendMessage);
2395 emscripten::function("agentGetChatHistory", &WasmControlApi::AgentGetChatHistory);
2396 emscripten::function("agentGetConfig", &WasmControlApi::AgentGetConfig);
2397 emscripten::function("agentSetConfig", &WasmControlApi::AgentSetConfig);
2398 emscripten::function("agentGetProviders", &WasmControlApi::AgentGetProviders);
2399 emscripten::function("agentGetProposals", &WasmControlApi::AgentGetProposals);
2400 emscripten::function("agentAcceptProposal", &WasmControlApi::AgentAcceptProposal);
2401 emscripten::function("agentRejectProposal", &WasmControlApi::AgentRejectProposal);
2402 emscripten::function("agentGetProposalDetails", &WasmControlApi::AgentGetProposalDetails);
2403 emscripten::function("agentOpenSidebar", &WasmControlApi::AgentOpenSidebar);
2404 emscripten::function("agentCloseSidebar", &WasmControlApi::AgentCloseSidebar);
2405}
2406
2407} // namespace platform
2408} // namespace app
2409} // namespace yaze
2410
2411#endif // __EMSCRIPTEN__
static WidgetIdRegistry & Instance()
struct snes_color snes_color
SNES color in 15-bit RGB format (BGR555)
#define LOG_INFO(category, format,...)
Definition log.h:105
EM_JS(void, CallJsAiDriver,(const char *history_json), { if(window.yaze &&window.yaze.ai &&window.yaze.ai.processAgentRequest) { window.yaze.ai.processAgentRequest(UTF8ToString(history_json));} else { console.error("AI Driver not found in window.yaze.ai.processAgentRequest");} })
constexpr std::array< const char *, 14 > kEditorNames
Definition editor.h:167
const char * GetCtrlDisplayName()
Get the display name for the primary modifier key.
std::string FormatCtrlShiftShortcut(ImGuiKey key)
Convenience function for Ctrl+Shift+key shortcuts.
Platform GetCurrentPlatform()
Get the current platform at runtime.
bool IsMacPlatform()
Check if running on macOS (native or web)
const char * GetAltDisplayName()
Get the display name for the secondary modifier key.
std::string FormatCtrlShortcut(ImGuiKey key)
Convenience function for Ctrl+key shortcuts.
constexpr int kNumOverworldMaps
Definition common.h:85
Room LoadRoomFromRom(Rom *rom, int room_id)
Definition room.cc:177
SNES color in 15-bit RGB format (BGR555)
Definition yaze.h:218
std::string togglePanel(std::string card_id)
Toggle a card's visibility.
std::string getPanelsInCategory(std::string category)
Get cards in a specific category.
std::string getRomStatus()
EMSCRIPTEN_BINDINGS(yaze_debug_inspector)
std::string readRomBytes(int address, int count)