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