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