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 // cards["room_selector"] = dungeon->show_room_selector_;
1540 // cards["room_matrix"] = dungeon->show_room_matrix_;
1541 // cards["entrances_list"] = dungeon->show_entrances_list_;
1542 // cards["room_graphics"] = dungeon->show_room_graphics_;
1543 // cards["object_editor"] = dungeon->show_object_editor_;
1544 // cards["palette_editor"] = dungeon->show_palette_editor_;
1545 // cards["debug_controls"] = dungeon->show_debug_controls_;
1546 // cards["control_panel"] = dungeon->show_control_panel_;
1547 result["visible_cards"] = cards;
1548
1549 return result.dump();
1550}
1551
1552std::string WasmControlApi::GetCurrentOverworldMap() {
1553 nlohmann::json result;
1554
1555 if (!IsReady()) {
1556 result["error"] = "Control API not initialized";
1557 return result.dump();
1558 }
1559
1560 auto* current = editor_manager_->GetCurrentEditor();
1561 if (!current || current->type() != editor::EditorType::kOverworld) {
1562 result["error"] = "Overworld editor not active";
1563 result["editor_type"] = current ? EditorTypeToString(static_cast<int>(current->type())) : "none";
1564 return result.dump();
1565 }
1566
1567 auto* editor_set = editor_manager_->GetCurrentEditorSet();
1568 if (!editor_set) {
1569 result["error"] = "No editor set available";
1570 return result.dump();
1571 }
1572
1573 auto* overworld = editor_set->GetOverworldEditor();
1574 if (!overworld) {
1575 result["error"] = "Overworld editor not available";
1576 return result.dump();
1577 }
1578 auto& ow_data = overworld->overworld();
1579
1580 result["map_id"] = ow_data.current_map_id();
1581 result["world"] = ow_data.current_world();
1582 result["world_name"] = ow_data.current_world() == 0 ? "Light World" :
1583 (ow_data.current_world() == 1 ? "Dark World" : "Special World");
1584 result["map_count"] = zelda3::kNumOverworldMaps;
1585
1586 return result.dump();
1587}
1588
1589std::string WasmControlApi::GetEditorSelection() {
1590 nlohmann::json result;
1591
1592 if (!IsReady()) {
1593 result["error"] = "Control API not initialized";
1594 return result.dump();
1595 }
1596
1597 auto* current = editor_manager_->GetCurrentEditor();
1598 if (!current) {
1599 result["error"] = "No editor active";
1600 return result.dump();
1601 }
1602
1603 result["editor_type"] = EditorTypeToString(static_cast<int>(current->type()));
1604 result["selection"] = nlohmann::json::array(); // Placeholder for future selection data
1605
1606 // TODO: Implement editor-specific selection queries
1607 // For now, return empty selection
1608 result["has_selection"] = false;
1609
1610 return result.dump();
1611}
1612
1613// ============================================================================
1614// Read-only Data APIs Implementation
1615// ============================================================================
1616
1617std::string WasmControlApi::GetRoomTileData(int room_id) {
1618 nlohmann::json result;
1619
1620 if (!IsReady()) {
1621 result["error"] = "Control API not initialized";
1622 return result.dump();
1623 }
1624
1625 if (room_id < 0 || room_id >= 296) {
1626 result["error"] = "Invalid room ID (must be 0-295)";
1627 return result.dump();
1628 }
1629
1630 auto* rom = editor_manager_->GetCurrentRom();
1631 if (!rom || !rom->is_loaded()) {
1632 result["error"] = "ROM not loaded";
1633 return result.dump();
1634 }
1635
1636 // Load room from ROM
1637 zelda3::Room room = zelda3::LoadRoomFromRom(rom, room_id);
1638 auto* game_data = editor_manager_->GetCurrentGameData();
1639 if (game_data) {
1640 room.SetGameData(game_data); // Ensure room has access to GameData
1641 }
1642 room.LoadRoomGraphics();
1643 room.LoadObjects();
1644
1645 result["room_id"] = room_id;
1646 result["width"] = 512;
1647 result["height"] = 512;
1648
1649 // Get layout objects for both layers
1650 const auto& layout = room.GetLayout();
1651 const auto& layout_objects = layout.GetObjects();
1652
1653 // Extract tile data for layer 1 and layer 2
1654 nlohmann::json layer1_tiles = nlohmann::json::array();
1655 nlohmann::json layer2_tiles = nlohmann::json::array();
1656
1657 for (const auto& obj : layout_objects) {
1658 nlohmann::json tile_obj;
1659 tile_obj["x"] = obj.x();
1660 tile_obj["y"] = obj.y();
1661
1662 auto tile_result = obj.GetTile(0);
1663 if (tile_result.ok()) {
1664 const auto* tile_info = tile_result.value();
1665 tile_obj["tile_id"] = tile_info->id_;
1666 tile_obj["palette"] = tile_info->palette_;
1667 tile_obj["priority"] = tile_info->over_;
1668 tile_obj["h_flip"] = tile_info->horizontal_mirror_;
1669 tile_obj["v_flip"] = tile_info->vertical_mirror_;
1670
1671 if (obj.GetLayerValue() == 1) {
1672 layer2_tiles.push_back(tile_obj);
1673 } else {
1674 layer1_tiles.push_back(tile_obj);
1675 }
1676 }
1677 }
1678
1679 result["layer1"] = layer1_tiles;
1680 result["layer2"] = layer2_tiles;
1681 result["layer1_count"] = layer1_tiles.size();
1682 result["layer2_count"] = layer2_tiles.size();
1683
1684 return result.dump();
1685}
1686
1687std::string WasmControlApi::GetRoomObjects(int room_id) {
1688 nlohmann::json result = nlohmann::json::array();
1689
1690 if (!IsReady()) {
1691 nlohmann::json error;
1692 error["error"] = "Control API not initialized";
1693 return error.dump();
1694 }
1695
1696 if (room_id < 0 || room_id >= 296) {
1697 nlohmann::json error;
1698 error["error"] = "Invalid room ID (must be 0-295)";
1699 return error.dump();
1700 }
1701
1702 auto* rom = editor_manager_->GetCurrentRom();
1703 if (!rom || !rom->is_loaded()) {
1704 nlohmann::json error;
1705 error["error"] = "ROM not loaded";
1706 return error.dump();
1707 }
1708
1709 // Load room from ROM
1710 zelda3::Room room = zelda3::LoadRoomFromRom(rom, room_id);
1711
1712 // Get tile objects from the room
1713 const auto& tile_objects = room.GetTileObjects();
1714
1715 for (const auto& obj : tile_objects) {
1716 nlohmann::json obj_data;
1717 obj_data["id"] = obj.id_;
1718 obj_data["x"] = obj.x();
1719 obj_data["y"] = obj.y();
1720 obj_data["size"] = obj.size();
1721 obj_data["layer"] = obj.GetLayerValue();
1722
1723 // Add object type information
1724 auto options = static_cast<int>(obj.options());
1725 obj_data["is_door"] = (options & static_cast<int>(zelda3::ObjectOption::Door)) != 0;
1726 obj_data["is_chest"] = (options & static_cast<int>(zelda3::ObjectOption::Chest)) != 0;
1727 obj_data["is_block"] = (options & static_cast<int>(zelda3::ObjectOption::Block)) != 0;
1728 obj_data["is_torch"] = (options & static_cast<int>(zelda3::ObjectOption::Torch)) != 0;
1729 obj_data["is_stairs"] = (options & static_cast<int>(zelda3::ObjectOption::Stairs)) != 0;
1730
1731 result.push_back(obj_data);
1732 }
1733
1734 return result.dump();
1735}
1736
1737std::string WasmControlApi::GetRoomProperties(int room_id) {
1738 nlohmann::json result;
1739
1740 if (!IsReady()) {
1741 result["error"] = "Control API not initialized";
1742 return result.dump();
1743 }
1744
1745 if (room_id < 0 || room_id >= 296) {
1746 result["error"] = "Invalid room ID (must be 0-295)";
1747 return result.dump();
1748 }
1749
1750 auto* rom = editor_manager_->GetCurrentRom();
1751 if (!rom || !rom->is_loaded()) {
1752 result["error"] = "ROM not loaded";
1753 return result.dump();
1754 }
1755
1756 // Load room from ROM
1757 zelda3::Room room = zelda3::LoadRoomFromRom(rom, room_id);
1758
1759 result["room_id"] = room_id;
1760 result["blockset"] = room.blockset();
1761 result["spriteset"] = room.spriteset();
1762 result["palette"] = room.palette();
1763 result["floor1"] = room.floor1();
1764 result["floor2"] = room.floor2();
1765 result["layout"] = room.layout_id();
1766 result["holewarp"] = room.holewarp();
1767 result["message_id"] = room.message_id();
1768
1769 // Effect and tags
1770 result["effect"] = static_cast<int>(room.effect());
1771 result["tag1"] = static_cast<int>(room.tag1());
1772 result["tag2"] = static_cast<int>(room.tag2());
1773 result["collision"] = static_cast<int>(room.collision());
1774
1775 // Layer merging info
1776 const auto& layer_merge = room.layer_merging();
1777 result["layer_merging"] = {
1778 {"id", layer_merge.ID},
1779 {"name", layer_merge.Name},
1780 {"layer2_visible", layer_merge.Layer2Visible},
1781 {"layer2_on_top", layer_merge.Layer2OnTop},
1782 {"layer2_translucent", layer_merge.Layer2Translucent}
1783 };
1784
1785 result["is_light"] = room.IsLight();
1786 result["is_loaded"] = room.IsLoaded();
1787
1788 return result.dump();
1789}
1790
1791std::string WasmControlApi::GetMapTileData(int map_id) {
1792 nlohmann::json result;
1793
1794 if (!IsReady()) {
1795 result["error"] = "Control API not initialized";
1796 return result.dump();
1797 }
1798
1799 if (map_id < 0 || map_id >= static_cast<int>(zelda3::kNumOverworldMaps)) {
1800 result["error"] = "Invalid map ID (must be 0-159)";
1801 return result.dump();
1802 }
1803
1804 auto* overworld = editor_manager_->overworld();
1805 if (!overworld) {
1806 result["error"] = "Overworld not loaded";
1807 return result.dump();
1808 }
1809
1810 auto* map = overworld->overworld_map(map_id);
1811 if (!map) {
1812 result["error"] = "Map not found";
1813 return result.dump();
1814 }
1815
1816 result["map_id"] = map_id;
1817 result["width"] = 32;
1818 result["height"] = 32;
1819
1820 // Get tile blockset data (this is the 32x32 tile16 data for the map)
1821 auto blockset = map->current_tile16_blockset();
1822
1823 // Instead of dumping all 1024 tiles, provide summary information
1824 result["has_tile_data"] = !blockset.empty();
1825 result["tile_count"] = blockset.size();
1826 result["is_built"] = map->is_built();
1827 result["is_large_map"] = map->is_large_map();
1828
1829 // Note: Full tile extraction would be very large (1024 tiles)
1830 // Only extract a small sample or provide it on request
1831 if (blockset.size() >= 64) {
1832 nlohmann::json sample_tiles = nlohmann::json::array();
1833 // Extract first 8x8 corner as a sample
1834 for (int i = 0; i < 64; i++) {
1835 sample_tiles.push_back(static_cast<int>(blockset[i]));
1836 }
1837 result["sample_tiles"] = sample_tiles;
1838 result["sample_note"] = "First 8x8 tiles from top-left corner";
1839 }
1840
1841 return result.dump();
1842}
1843
1844std::string WasmControlApi::GetMapEntities(int map_id) {
1845 nlohmann::json result;
1846
1847 if (!IsReady()) {
1848 result["error"] = "Control API not initialized";
1849 return result.dump();
1850 }
1851
1852 if (map_id < 0 || map_id >= static_cast<int>(zelda3::kNumOverworldMaps)) {
1853 result["error"] = "Invalid map ID (must be 0-159)";
1854 return result.dump();
1855 }
1856
1857 auto* overworld = editor_manager_->overworld();
1858 if (!overworld) {
1859 result["error"] = "Overworld not loaded";
1860 return result.dump();
1861 }
1862
1863 result["map_id"] = map_id;
1864 result["entrances"] = nlohmann::json::array();
1865 result["exits"] = nlohmann::json::array();
1866 result["items"] = nlohmann::json::array();
1867 result["sprites"] = nlohmann::json::array();
1868
1869 // Get entrances for this map
1870 for (const auto& entrance : overworld->entrances()) {
1871 if (entrance.map_id_ == static_cast<uint16_t>(map_id)) {
1872 nlohmann::json e;
1873 e["id"] = entrance.entrance_id_;
1874 e["x"] = entrance.x_;
1875 e["y"] = entrance.y_;
1876 e["map_id"] = entrance.map_id_;
1877 result["entrances"].push_back(e);
1878 }
1879 }
1880
1881 // Get exits for this map
1882 auto* exits = overworld->exits();
1883 if (exits) {
1884 for (const auto& exit : *exits) {
1885 if (exit.map_id_ == static_cast<uint16_t>(map_id)) {
1886 nlohmann::json ex;
1887 ex["x"] = exit.x_;
1888 ex["y"] = exit.y_;
1889 ex["map_id"] = exit.map_id_;
1890 ex["room_id"] = exit.room_id_;
1891 result["exits"].push_back(ex);
1892 }
1893 }
1894 }
1895
1896 // Get items for this map (using map_id_ from GameEntity base class)
1897 for (const auto& item : overworld->all_items()) {
1898 if (!item.deleted && item.map_id_ == static_cast<uint16_t>(map_id)) {
1899 nlohmann::json i;
1900 i["id"] = item.id_;
1901 i["x"] = item.x_;
1902 i["y"] = item.y_;
1903 result["items"].push_back(i);
1904 }
1905 }
1906
1907 return result.dump();
1908}
1909
1910std::string WasmControlApi::GetMapProperties(int map_id) {
1911 nlohmann::json result;
1912
1913 if (!IsReady()) {
1914 result["error"] = "Control API not initialized";
1915 return result.dump();
1916 }
1917
1918 if (map_id < 0 || map_id >= static_cast<int>(zelda3::kNumOverworldMaps)) {
1919 result["error"] = "Invalid map ID (must be 0-159)";
1920 return result.dump();
1921 }
1922
1923 auto* overworld = editor_manager_->overworld();
1924 if (!overworld) {
1925 result["error"] = "Overworld not loaded";
1926 return result.dump();
1927 }
1928
1929 auto* map = overworld->overworld_map(map_id);
1930 if (!map) {
1931 result["error"] = "Map not found";
1932 return result.dump();
1933 }
1934
1935 result["map_id"] = map_id;
1936 result["world"] = map_id / 64;
1937 result["parent_id"] = map->parent();
1938 result["area_graphics"] = map->area_graphics();
1939 result["area_palette"] = map->area_palette();
1940 result["sprite_graphics"] = {map->sprite_graphics(0), map->sprite_graphics(1), map->sprite_graphics(2)};
1941 result["sprite_palette"] = {map->sprite_palette(0), map->sprite_palette(1), map->sprite_palette(2)};
1942 result["message_id"] = map->message_id();
1943 result["is_large_map"] = map->is_large_map();
1944
1945 return result.dump();
1946}
1947
1948std::string WasmControlApi::GetPaletteData(const std::string& group_name, int palette_id) {
1949 nlohmann::json result;
1950
1951 if (!IsReady()) {
1952 result["error"] = "Control API not initialized";
1953 return result.dump();
1954 }
1955
1956 auto* rom = editor_manager_->GetCurrentRom();
1957 if (!rom || !rom->is_loaded()) {
1958 result["error"] = "ROM not loaded";
1959 return result.dump();
1960 }
1961
1962 result["group"] = group_name;
1963 result["palette_id"] = palette_id;
1964
1965 try {
1966 auto* game_data = editor_manager_->GetCurrentGameData();
1967 if (!game_data) {
1968 result["error"] = "GameData not available";
1969 return result.dump();
1970 }
1971 auto* group = game_data->palette_groups.get_group(group_name);
1972
1973 if (!group) {
1974 result["error"] = "Invalid palette group name";
1975 return result.dump();
1976 }
1977
1978 if (palette_id < 0 || palette_id >= static_cast<int>(group->size())) {
1979 result["error"] = "Invalid palette ID for this group";
1980 result["max_palette_id"] = group->size() - 1;
1981 return result.dump();
1982 }
1983
1984 auto palette = (*group)[palette_id];
1985 nlohmann::json colors = nlohmann::json::array();
1986
1987 // Extract color values
1988 for (size_t i = 0; i < palette.size(); i++) {
1989 const auto& color = palette[i];
1990 nlohmann::json color_data;
1991 color_data["index"] = i;
1992
1993 // Convert SNES color to RGB
1994 auto snes_color = color.snes();
1995 auto rgb_color = color.rgb();
1996
1997 // ImVec4 uses x,y,z,w for r,g,b,a in 0.0-1.0 range
1998 int r = static_cast<int>(rgb_color.x * 255);
1999 int g = static_cast<int>(rgb_color.y * 255);
2000 int b = static_cast<int>(rgb_color.z * 255);
2001 color_data["r"] = r;
2002 color_data["g"] = g;
2003 color_data["b"] = b;
2004 color_data["hex"] = absl::StrFormat("#%02X%02X%02X", r, g, b);
2005 color_data["snes_value"] = snes_color;
2006
2007 colors.push_back(color_data);
2008 }
2009
2010 result["colors"] = colors;
2011 result["color_count"] = palette.size();
2012
2013 } catch (const std::exception& e) {
2014 result["error"] = std::string("Failed to extract palette: ") + e.what();
2015 }
2016
2017 return result.dump();
2018}
2019
2020std::string WasmControlApi::ListPaletteGroups() {
2021 nlohmann::json result = nlohmann::json::array();
2022
2023 // List available palette groups (matching PaletteGroupMap structure)
2024 result.push_back("ow_main");
2025 result.push_back("ow_aux");
2026 result.push_back("ow_animated");
2027 result.push_back("hud");
2028 result.push_back("global_sprites");
2029 result.push_back("armors");
2030 result.push_back("swords");
2031 result.push_back("shields");
2032 result.push_back("sprites_aux1");
2033 result.push_back("sprites_aux2");
2034 result.push_back("sprites_aux3");
2035 result.push_back("dungeon_main");
2036 result.push_back("grass");
2037 result.push_back("3d_object");
2038 result.push_back("ow_mini_map");
2039
2040 return result.dump();
2041}
2042
2043std::string WasmControlApi::LoadFont(const std::string& name, const std::string& data, float size) {
2044 nlohmann::json result;
2045 auto status = yaze::platform::WasmSettings::LoadUserFont(name, data, size);
2046 if (status.ok()) {
2047 result["success"] = true;
2048 } else {
2049 result["success"] = false;
2050 result["error"] = status.ToString();
2051 }
2052 return result.dump();
2053}
2054
2055// ============================================================================
2056// GUI Automation APIs Implementation
2057// ============================================================================
2058
2059std::string WasmControlApi::GetUIElementTree() {
2060 nlohmann::json result;
2061
2062 if (!IsReady()) {
2063 result["error"] = "Control API not initialized";
2064 result["elements"] = nlohmann::json::array();
2065 return result.dump();
2066 }
2067
2068 // Query the WidgetIdRegistry for all registered widgets
2069 auto& registry = gui::WidgetIdRegistry::Instance();
2070 const auto& all_widgets = registry.GetAllWidgets();
2071
2072 nlohmann::json elements = nlohmann::json::array();
2073
2074 // Convert WidgetInfo to JSON elements
2075 for (const auto& [path, info] : all_widgets) {
2076 nlohmann::json elem;
2077 elem["id"] = info.full_path;
2078 elem["type"] = info.type;
2079 elem["label"] = info.label;
2080 elem["enabled"] = info.enabled;
2081 elem["visible"] = info.visible;
2082 elem["window"] = info.window_name;
2083
2084 // Add bounds if available
2085 if (info.bounds.valid) {
2086 elem["bounds"] = {
2087 {"x", info.bounds.min_x},
2088 {"y", info.bounds.min_y},
2089 {"width", info.bounds.max_x - info.bounds.min_x},
2090 {"height", info.bounds.max_y - info.bounds.min_y}
2091 };
2092 } else {
2093 elem["bounds"] = {
2094 {"x", 0}, {"y", 0}, {"width", 0}, {"height", 0}
2095 };
2096 }
2097
2098 // Add metadata
2099 if (!info.description.empty()) {
2100 elem["description"] = info.description;
2101 }
2102 elem["imgui_id"] = static_cast<uint32_t>(info.imgui_id);
2103 elem["last_seen_frame"] = info.last_seen_frame;
2104
2105 elements.push_back(elem);
2106 }
2107
2108 result["elements"] = elements;
2109 result["count"] = elements.size();
2110 result["source"] = "WidgetIdRegistry";
2111
2112 return result.dump();
2113}
2114
2115std::string WasmControlApi::GetUIElementBounds(const std::string& element_id) {
2116 nlohmann::json result;
2117
2118 if (!IsReady()) {
2119 result["error"] = "Control API not initialized";
2120 return result.dump();
2121 }
2122
2123 // Query the WidgetIdRegistry for the specific widget
2124 auto& registry = gui::WidgetIdRegistry::Instance();
2125 const auto* widget_info = registry.GetWidgetInfo(element_id);
2126
2127 result["id"] = element_id;
2128
2129 if (widget_info == nullptr) {
2130 result["found"] = false;
2131 result["error"] = "Element not found: " + element_id;
2132 return result.dump();
2133 }
2134
2135 result["found"] = true;
2136 result["visible"] = widget_info->visible;
2137 result["enabled"] = widget_info->enabled;
2138 result["type"] = widget_info->type;
2139 result["label"] = widget_info->label;
2140 result["window"] = widget_info->window_name;
2141
2142 // Add bounds if available
2143 if (widget_info->bounds.valid) {
2144 result["x"] = widget_info->bounds.min_x;
2145 result["y"] = widget_info->bounds.min_y;
2146 result["width"] = widget_info->bounds.max_x - widget_info->bounds.min_x;
2147 result["height"] = widget_info->bounds.max_y - widget_info->bounds.min_y;
2148 result["bounds_valid"] = true;
2149 } else {
2150 result["x"] = 0;
2151 result["y"] = 0;
2152 result["width"] = 0;
2153 result["height"] = 0;
2154 result["bounds_valid"] = false;
2155 }
2156
2157 // Add metadata
2158 result["imgui_id"] = static_cast<uint32_t>(widget_info->imgui_id);
2159 result["last_seen_frame"] = widget_info->last_seen_frame;
2160
2161 if (!widget_info->description.empty()) {
2162 result["description"] = widget_info->description;
2163 }
2164
2165 return result.dump();
2166}
2167
2168std::string WasmControlApi::SetSelection(const std::string& ids_json) {
2169 nlohmann::json result;
2170
2171 if (!IsReady()) {
2172 result["success"] = false;
2173 result["error"] = "Control API not initialized";
2174 return result.dump();
2175 }
2176
2177 try {
2178 auto ids = nlohmann::json::parse(ids_json);
2179
2180 // TODO: Implement actual selection setting based on active editor
2181 // For now, return success with the IDs that would be selected
2182 result["success"] = true;
2183 result["selected_ids"] = ids;
2184 result["note"] = "Selection setting not yet fully implemented";
2185
2186 } catch (const std::exception& e) {
2187 result["success"] = false;
2188 result["error"] = std::string("Invalid JSON: ") + e.what();
2189 }
2190
2191 return result.dump();
2192}
2193
2194// ============================================================================
2195// Platform Info API Implementation
2196// ============================================================================
2197
2198std::string WasmControlApi::GetPlatformInfo() {
2199 nlohmann::json result;
2200
2201 // Get current platform from runtime detection
2202 auto platform = gui::GetCurrentPlatform();
2203
2204 // Convert platform enum to string
2205 switch (platform) {
2207 result["platform"] = "Windows";
2208 break;
2210 result["platform"] = "macOS";
2211 break;
2213 result["platform"] = "Linux";
2214 break;
2216 result["platform"] = "WebMac";
2217 break;
2219 result["platform"] = "WebOther";
2220 break;
2221 default:
2222 result["platform"] = "Unknown";
2223 break;
2224 }
2225
2226 // Get platform-specific display names for modifiers
2227 result["is_mac"] = gui::IsMacPlatform();
2228 result["ctrl_display"] = gui::GetCtrlDisplayName();
2229 result["alt_display"] = gui::GetAltDisplayName();
2230 result["shift_display"] = "Shift";
2231
2232 // Example shortcut formatting
2233 result["example_save"] = gui::FormatCtrlShortcut(ImGuiKey_S);
2234 result["example_open"] = gui::FormatCtrlShortcut(ImGuiKey_O);
2235 result["example_command_palette"] = gui::FormatCtrlShiftShortcut(ImGuiKey_P);
2236
2237 return result.dump();
2238}
2239
2240// ============================================================================
2241// Agent API Implementations
2242// ============================================================================
2243
2244bool WasmControlApi::AgentIsReady() {
2245 if (!initialized_ || !editor_manager_) {
2246 return false;
2247 }
2248 // Check if agent editor is available
2249 auto* agent_editor = editor_manager_->GetAgentEditor();
2250 return agent_editor != nullptr;
2251}
2252
2253std::string WasmControlApi::AgentSendMessage(const std::string& message) {
2254 nlohmann::json result;
2255
2256 if (!initialized_ || !editor_manager_) {
2257 result["success"] = false;
2258 result["error"] = "API not initialized";
2259 return result.dump();
2260 }
2261
2262 auto* agent_editor = editor_manager_->GetAgentEditor();
2263 if (!agent_editor) {
2264 result["success"] = false;
2265 result["error"] = "Agent editor not available";
2266 return result.dump();
2267 }
2268
2269 auto* agent_chat = agent_editor->GetAgentChat();
2270 if (!agent_chat) {
2271 result["success"] = false;
2272 result["error"] = "Agent chat not available";
2273 return result.dump();
2274 }
2275
2276 // Queue the message for the agent
2277 // The actual processing happens asynchronously
2278 result["success"] = true;
2279 result["status"] = "queued";
2280 result["message"] = message;
2281
2282 // Note: Actual message sending will be handled by the agent chat
2283 // This API provides the interface for web-based agents to interact
2284
2285 return result.dump();
2286}
2287
2288std::string WasmControlApi::AgentGetChatHistory() {
2289 nlohmann::json result = nlohmann::json::array();
2290
2291 if (!initialized_ || !editor_manager_) {
2292 return result.dump();
2293 }
2294
2295 auto* agent_editor = editor_manager_->GetAgentEditor();
2296 if (!agent_editor) {
2297 return result.dump();
2298 }
2299
2300 auto* agent_chat = agent_editor->GetAgentChat();
2301 if (!agent_chat) {
2302 return result.dump();
2303 }
2304
2305 // Get chat history from the agent chat
2306 // For now, return empty array - full implementation requires
2307 // AgentChat to expose history via a public method
2308
2309 return result.dump();
2310}
2311
2312std::string WasmControlApi::AgentGetConfig() {
2313 nlohmann::json result;
2314
2315 if (!initialized_ || !editor_manager_) {
2316 result["error"] = "API not initialized";
2317 return result.dump();
2318 }
2319
2320 auto* agent_editor = editor_manager_->GetAgentEditor();
2321 if (!agent_editor) {
2322 result["error"] = "Agent editor not available";
2323 return result.dump();
2324 }
2325
2326 auto config = agent_editor->GetCurrentConfig();
2327 result["provider"] = config.provider;
2328 result["model"] = config.model;
2329 result["ollama_host"] = config.ollama_host;
2330 result["verbose"] = config.verbose;
2331 result["show_reasoning"] = config.show_reasoning;
2332 result["max_tool_iterations"] = config.max_tool_iterations;
2333
2334 return result.dump();
2335}
2336
2337std::string WasmControlApi::AgentSetConfig(const std::string& config_json) {
2338 nlohmann::json result;
2339
2340 if (!initialized_ || !editor_manager_) {
2341 result["success"] = false;
2342 result["error"] = "API not initialized";
2343 return result.dump();
2344 }
2345
2346 auto* agent_editor = editor_manager_->GetAgentEditor();
2347 if (!agent_editor) {
2348 result["success"] = false;
2349 result["error"] = "Agent editor not available";
2350 return result.dump();
2351 }
2352
2353 try {
2354 auto config_data = nlohmann::json::parse(config_json);
2355
2356 editor::AgentEditor::AgentConfig config;
2357 if (config_data.contains("provider")) {
2358 config.provider = config_data["provider"].get<std::string>();
2359 }
2360 if (config_data.contains("model")) {
2361 config.model = config_data["model"].get<std::string>();
2362 }
2363 if (config_data.contains("ollama_host")) {
2364 config.ollama_host = config_data["ollama_host"].get<std::string>();
2365 }
2366 if (config_data.contains("verbose")) {
2367 config.verbose = config_data["verbose"].get<bool>();
2368 }
2369 if (config_data.contains("show_reasoning")) {
2370 config.show_reasoning = config_data["show_reasoning"].get<bool>();
2371 }
2372 if (config_data.contains("max_tool_iterations")) {
2373 config.max_tool_iterations = config_data["max_tool_iterations"].get<int>();
2374 }
2375
2376 agent_editor->ApplyConfig(config);
2377 result["success"] = true;
2378 } catch (const std::exception& e) {
2379 result["success"] = false;
2380 result["error"] = e.what();
2381 }
2382
2383 return result.dump();
2384}
2385
2386std::string WasmControlApi::AgentGetProviders() {
2387 nlohmann::json result = nlohmann::json::array();
2388
2389 // List available AI providers
2390 result.push_back({
2391 {"id", "mock"},
2392 {"name", "Mock Provider"},
2393 {"description", "Testing provider that echoes messages"}
2394 });
2395 result.push_back({
2396 {"id", "ollama"},
2397 {"name", "Ollama"},
2398 {"description", "Local Ollama server"},
2399 {"requires_host", true}
2400 });
2401 result.push_back({
2402 {"id", "gemini"},
2403 {"name", "Google Gemini"},
2404 {"description", "Google's Gemini API"},
2405 {"requires_api_key", true}
2406 });
2407
2408 return result.dump();
2409}
2410
2411std::string WasmControlApi::AgentGetProposals() {
2412 nlohmann::json result = nlohmann::json::array();
2413
2414 if (!initialized_ || !editor_manager_) {
2415 return result.dump();
2416 }
2417
2418 // TODO: Integrate with proposal system when available
2419 // For now, return empty array
2420
2421 return result.dump();
2422}
2423
2424std::string WasmControlApi::AgentAcceptProposal(const std::string& proposal_id) {
2425 nlohmann::json result;
2426
2427 if (!initialized_ || !editor_manager_) {
2428 result["success"] = false;
2429 result["error"] = "API not initialized";
2430 return result.dump();
2431 }
2432
2433 // TODO: Integrate with proposal system when available
2434 result["success"] = false;
2435 result["error"] = "Proposal system not yet integrated";
2436 result["proposal_id"] = proposal_id;
2437
2438 return result.dump();
2439}
2440
2441std::string WasmControlApi::AgentRejectProposal(const std::string& proposal_id) {
2442 nlohmann::json result;
2443
2444 if (!initialized_ || !editor_manager_) {
2445 result["success"] = false;
2446 result["error"] = "API not initialized";
2447 return result.dump();
2448 }
2449
2450 // TODO: Integrate with proposal system when available
2451 result["success"] = false;
2452 result["error"] = "Proposal system not yet integrated";
2453 result["proposal_id"] = proposal_id;
2454
2455 return result.dump();
2456}
2457
2458std::string WasmControlApi::AgentGetProposalDetails(const std::string& proposal_id) {
2459 nlohmann::json result;
2460
2461 if (!initialized_ || !editor_manager_) {
2462 result["error"] = "API not initialized";
2463 return result.dump();
2464 }
2465
2466 // TODO: Integrate with proposal system when available
2467 result["error"] = "Proposal system not yet integrated";
2468 result["proposal_id"] = proposal_id;
2469
2470 return result.dump();
2471}
2472
2473std::string WasmControlApi::AgentOpenSidebar() {
2474 nlohmann::json result;
2475
2476 if (!initialized_ || !editor_manager_) {
2477 result["success"] = false;
2478 result["error"] = "API not initialized";
2479 return result.dump();
2480 }
2481
2482 auto* agent_editor = editor_manager_->GetAgentEditor();
2483 if (!agent_editor) {
2484 result["success"] = false;
2485 result["error"] = "Agent editor not available";
2486 return result.dump();
2487 }
2488
2489 agent_editor->SetChatActive(true);
2490 result["success"] = true;
2491 result["sidebar_open"] = true;
2492
2493 return result.dump();
2494}
2495
2496std::string WasmControlApi::AgentCloseSidebar() {
2497 nlohmann::json result;
2498
2499 if (!initialized_ || !editor_manager_) {
2500 result["success"] = false;
2501 result["error"] = "API not initialized";
2502 return result.dump();
2503 }
2504
2505 auto* agent_editor = editor_manager_->GetAgentEditor();
2506 if (!agent_editor) {
2507 result["success"] = false;
2508 result["error"] = "Agent editor not available";
2509 return result.dump();
2510 }
2511
2512 agent_editor->SetChatActive(false);
2513 result["success"] = true;
2514 result["sidebar_open"] = false;
2515
2516 return result.dump();
2517}
2518
2519// ============================================================================
2520// Emscripten Bindings
2521// ============================================================================
2522
2523EMSCRIPTEN_BINDINGS(wasm_control_api) {
2524 emscripten::function("controlIsReady", &WasmControlApi::IsReady);
2525 emscripten::function("controlSwitchEditor", &WasmControlApi::SwitchEditor);
2526 emscripten::function("controlGetCurrentEditor", &WasmControlApi::GetCurrentEditor);
2527 emscripten::function("controlGetAvailableEditors", &WasmControlApi::GetAvailableEditors);
2528 emscripten::function("controlOpenWindow", &WasmControlApi::OpenWindow);
2529 emscripten::function("controlOpenPanel", &WasmControlApi::OpenPanel);
2530 emscripten::function("controlCloseWindow", &WasmControlApi::CloseWindow);
2531 emscripten::function("controlClosePanel", &WasmControlApi::ClosePanel);
2532 emscripten::function("controlToggleWindow", &WasmControlApi::ToggleWindow);
2533 emscripten::function("controlTogglePanel", &WasmControlApi::TogglePanel);
2534 emscripten::function("controlGetVisibleWindows", &WasmControlApi::GetVisibleWindows);
2535 emscripten::function("controlGetVisiblePanels", &WasmControlApi::GetVisiblePanels);
2536 emscripten::function("controlGetAvailableWindows", &WasmControlApi::GetAvailableWindows);
2537 emscripten::function("controlGetAvailablePanels", &WasmControlApi::GetAvailablePanels);
2538 emscripten::function("controlGetWindowsInCategory", &WasmControlApi::GetWindowsInCategory);
2539 emscripten::function("controlGetPanelsInCategory", &WasmControlApi::GetPanelsInCategory);
2540 emscripten::function("controlShowAllWindows", &WasmControlApi::ShowAllWindows);
2541 emscripten::function("controlHideAllWindows", &WasmControlApi::HideAllWindows);
2542 emscripten::function("controlSetPanelLayout", &WasmControlApi::SetPanelLayout);
2543 emscripten::function("controlGetAvailableLayouts", &WasmControlApi::GetAvailableLayouts);
2544 emscripten::function("controlSaveCurrentLayout", &WasmControlApi::SaveCurrentLayout);
2545 emscripten::function("controlGetAvailableMenuActions", &WasmControlApi::GetAvailableMenuActions);
2546 emscripten::function("controlToggleMenuBar", &WasmControlApi::ToggleMenuBar);
2547 emscripten::function("controlGetSessionInfo", &WasmControlApi::GetSessionInfo);
2548 emscripten::function("controlCreateSession", &WasmControlApi::CreateSession);
2549 emscripten::function("controlSwitchSession", &WasmControlApi::SwitchSession);
2550 emscripten::function("controlGetRomStatus", &WasmControlApi::GetRomStatus);
2551 emscripten::function("controlReadRomBytes", &WasmControlApi::ReadRomBytes);
2552 emscripten::function("controlWriteRomBytes", &WasmControlApi::WriteRomBytes);
2553 emscripten::function("controlSaveRom", &WasmControlApi::SaveRom);
2554
2555 // Editor State APIs
2556 emscripten::function("editorGetSnapshot", &WasmControlApi::GetEditorSnapshot);
2557 emscripten::function("editorGetCurrentDungeonRoom", &WasmControlApi::GetCurrentDungeonRoom);
2558 emscripten::function("editorGetCurrentOverworldMap", &WasmControlApi::GetCurrentOverworldMap);
2559 emscripten::function("editorGetSelection", &WasmControlApi::GetEditorSelection);
2560
2561 // Read-only Data APIs
2562 emscripten::function("dataGetRoomTileData", &WasmControlApi::GetRoomTileData);
2563 emscripten::function("dataGetRoomObjects", &WasmControlApi::GetRoomObjects);
2564 emscripten::function("dataGetRoomProperties", &WasmControlApi::GetRoomProperties);
2565 emscripten::function("dataGetMapTileData", &WasmControlApi::GetMapTileData);
2566 emscripten::function("dataGetMapEntities", &WasmControlApi::GetMapEntities);
2567 emscripten::function("dataGetMapProperties", &WasmControlApi::GetMapProperties);
2568 emscripten::function("dataGetPaletteData", &WasmControlApi::GetPaletteData);
2569 emscripten::function("dataListPaletteGroups", &WasmControlApi::ListPaletteGroups);
2570
2571 // GUI Automation APIs
2572 emscripten::function("guiGetUIElementTree", &WasmControlApi::GetUIElementTree);
2573 emscripten::function("guiGetUIElementBounds", &WasmControlApi::GetUIElementBounds);
2574 emscripten::function("guiSetSelection", &WasmControlApi::SetSelection);
2575
2576 // Settings APIs
2577 emscripten::function("settingsGetCurrentThemeData", &yaze::platform::WasmSettings::GetCurrentThemeData);
2578 emscripten::function("settingsLoadFont", &WasmControlApi::LoadFont);
2579
2580 // Platform Info API
2581 emscripten::function("controlGetPlatformInfo", &WasmControlApi::GetPlatformInfo);
2582
2583 // Agent API
2584 emscripten::function("agentIsReady", &WasmControlApi::AgentIsReady);
2585 emscripten::function("agentSendMessage", &WasmControlApi::AgentSendMessage);
2586 emscripten::function("agentGetChatHistory", &WasmControlApi::AgentGetChatHistory);
2587 emscripten::function("agentGetConfig", &WasmControlApi::AgentGetConfig);
2588 emscripten::function("agentSetConfig", &WasmControlApi::AgentSetConfig);
2589 emscripten::function("agentGetProviders", &WasmControlApi::AgentGetProviders);
2590 emscripten::function("agentGetProposals", &WasmControlApi::AgentGetProposals);
2591 emscripten::function("agentAcceptProposal", &WasmControlApi::AgentAcceptProposal);
2592 emscripten::function("agentRejectProposal", &WasmControlApi::AgentRejectProposal);
2593 emscripten::function("agentGetProposalDetails", &WasmControlApi::AgentGetProposalDetails);
2594 emscripten::function("agentOpenSidebar", &WasmControlApi::AgentOpenSidebar);
2595 emscripten::function("agentCloseSidebar", &WasmControlApi::AgentCloseSidebar);
2596}
2597
2598} // namespace platform
2599} // namespace app
2600} // namespace yaze
2601
2602#endif // __EMSCRIPTEN__
auto filename() const
Definition rom.h:145
absl::Status WriteByte(int addr, uint8_t value)
Definition rom.cc:476
absl::StatusOr< uint8_t > ReadByte(int offset) const
Definition rom.cc:408
auto size() const
Definition rom.h:138
bool dirty() const
Definition rom.h:133
bool is_loaded() const
Definition rom.h:132
auto title() const
Definition rom.h:137
EditorType type() const
Definition editor.h:293
static WidgetIdRegistry & Instance()
#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");} })
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 *, 14 > kEditorNames
Definition editor.h:221
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:325
SNES color in 15-bit RGB format (BGR555)
PaletteGroup * get_group(const std::string &group_name)
gfx::PaletteGroupMap palette_groups
Definition game_data.h:91
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)