package cc.unitmesh.viewer.web.webedit import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json /** * JVM implementation of WebEditBridge using WebViewNavigator */ class JvmWebEditBridge : WebEditBridge { private val _currentUrl = MutableStateFlow("") override val currentUrl: StateFlow = _currentUrl.asStateFlow() private val _pageTitle = MutableStateFlow("") override val pageTitle: StateFlow = _pageTitle.asStateFlow() private val _isLoading = MutableStateFlow(false) override val isLoading: StateFlow = _isLoading.asStateFlow() private val _loadProgress = MutableStateFlow(0) override val loadProgress: StateFlow = _loadProgress.asStateFlow() private val _domTree = MutableStateFlow(null) override val domTree: StateFlow = _domTree.asStateFlow() private val _selectedElement = MutableStateFlow(null) override val selectedElement: StateFlow = _selectedElement.asStateFlow() private val _d2SnapTree = MutableStateFlow(null) override val d2SnapTree: StateFlow = _d2SnapTree.asStateFlow() private val _accessibilityTree = MutableStateFlow(null) override val accessibilityTree: StateFlow = _accessibilityTree.asStateFlow() private val _actionableElements = MutableStateFlow>(emptyList()) override val actionableElements: StateFlow> = _actionableElements.asStateFlow() private val _lastActionResult = MutableStateFlow(null) override val lastActionResult: StateFlow = _lastActionResult.asStateFlow() private val _lastScreenshot = MutableStateFlow(null) override val lastScreenshot: StateFlow = _lastScreenshot.asStateFlow() private val _isSelectionMode = MutableStateFlow(false) override val isSelectionMode: StateFlow = _isSelectionMode.asStateFlow() private val _isReady = MutableStateFlow(false) override val isReady: StateFlow = _isReady.asStateFlow() private val _errorMessage = MutableStateFlow(null) override val errorMessage: StateFlow = _errorMessage.asStateFlow() private val json = Json { encodeDefaults = true; ignoreUnknownKeys = true } // Callback to execute JavaScript in WebView var executeJavaScript: ((String) -> Unit)? = null // Callback to navigate in WebView var navigateCallback: ((String) -> Unit)? = null var reloadCallback: (() -> Unit)? = null var goBackCallback: (() -> Unit)? = null var goForwardCallback: (() -> Unit)? = null override suspend fun navigateTo(url: String) { println("[JvmWebEditBridge] 🚀 navigateTo called: '$url'") _isLoading.value = true _currentUrl.value = url _errorMessage.value = null // Clear previous errors try { println("[JvmWebEditBridge] 📞 Calling navigateCallback...") navigateCallback?.invoke(url) ?: println("[JvmWebEditBridge] ⚠️ navigateCallback is null!") println("[JvmWebEditBridge] ✅ navigateCallback invoked") } catch (e: Exception) { val errorMsg = "Failed to navigate: ${e.message}" println("[JvmWebEditBridge] ❌ Navigation error: $errorMsg") e.printStackTrace() _errorMessage.value = errorMsg _isLoading.value = false } } override suspend fun reload() { _isLoading.value = true reloadCallback?.invoke() } override suspend fun goBack() { goBackCallback?.invoke() } override suspend fun goForward() { goForwardCallback?.invoke() } override suspend fun setSelectionMode(enabled: Boolean) { println("[JvmWebEditBridge] 🎯 setSelectionMode: $enabled") _isSelectionMode.value = enabled val script = "window.webEditBridge?.setSelectionMode($enabled);" println("[JvmWebEditBridge] 📜 Executing JS: $script") executeJavaScript?.invoke(script) ?: println("[JvmWebEditBridge] ⚠️ executeJavaScript is null!") } override suspend fun enableInspectMode() { _isSelectionMode.value = true val script = "window.webEditBridge?.enableInspectMode();" executeJavaScript?.invoke(script) } override suspend fun disableInspectMode() { _isSelectionMode.value = false val script = "window.webEditBridge?.disableInspectMode();" executeJavaScript?.invoke(script) } override suspend fun highlightElement(selector: String) { val escapedSelector = selector .replace("\\", "\\\\") .replace("'", "\\'") .replace("\"", "\\\"") .replace("\n", "\\n") .replace("\r", "\\r") val script = "window.webEditBridge?.highlightElement('$escapedSelector');" executeJavaScript?.invoke(script) } override suspend fun clearHighlights() { val script = "window.webEditBridge?.clearHighlights();" executeJavaScript?.invoke(script) } override suspend fun scrollToElement(selector: String) { val escapedSelector = selector .replace("\\", "\\\\") .replace("'", "\\'") .replace("\"", "\\\"") .replace("\n", "\\n") .replace("\r", "\\r") val script = "window.webEditBridge?.scrollToElement('$escapedSelector');" executeJavaScript?.invoke(script) } override suspend fun refreshDOMTree() { val script = "window.webEditBridge?.getDOMTree();" executeJavaScript?.invoke(script) } override suspend fun refreshD2SnapTree() { val script = "window.webEditBridge?.getD2SnapTree();" executeJavaScript?.invoke(script) } override suspend fun refreshAccessibilityTree() { val script = "window.webEditBridge?.getAccessibilityTree();" executeJavaScript?.invoke(script) } override suspend fun refreshActionableElements() { val script = "window.webEditBridge?.getActionableElements();" executeJavaScript?.invoke(script) } override suspend fun getElementAtPoint(x: Int, y: Int): DOMElement? { // TODO: Implement using JavaScript callback mechanism similar to getSelectedElementHtml // This requires invoking JS to call window.webEditBridge.getElementAtPoint(x, y) // and receiving the result via a callback or promise-based bridge. throw NotImplementedError("getElementAtPoint requires callback mechanism - not yet implemented") } /** * Get HTML content of selected element. * TODO: This is currently a stub. Implement using JavaScript callback mechanism * to properly retrieve outerHTML from the WebView. */ override suspend fun getSelectedElementHtml(): String? { // Stub implementation - actual retrieval requires callback mechanism return null } override suspend fun performAction(action: WebEditAction) { val payload = json.encodeToString(action) executeJavaScript?.invoke("window.webEditBridge?.performAction($payload);") } override suspend fun click(selector: String) { performAction(WebEditAction(action = "click", selector = selector)) } override suspend fun typeText(selector: String, text: String, clearFirst: Boolean) { performAction(WebEditAction(action = "type", selector = selector, text = text, clearFirst = clearFirst)) } override suspend fun selectOption(selector: String, value: String) { performAction(WebEditAction(action = "select", selector = selector, value = value)) } override suspend fun pressKey(key: String, selector: String?) { performAction(WebEditAction(action = "pressKey", selector = selector, key = key)) } override suspend fun captureScreenshot(maxWidth: Int, quality: Double) { println("[JvmWebEditBridge] 📸 captureScreenshot: maxWidth=$maxWidth, quality=$quality") val script = "window.webEditBridge?.captureScreenshot({ maxWidth: $maxWidth, quality: $quality });" executeJavaScript?.invoke(script) } override fun markReady() { println("[JvmWebEditBridge] ✅ Bridge marked as READY") _isReady.value = true } override fun handleMessage(message: WebEditMessage) { println("[JvmWebEditBridge] 📨 handleMessage: ${message::class.simpleName}") when (message) { is WebEditMessage.DOMTreeUpdated -> { println("[JvmWebEditBridge] 🌳 DOM Tree Updated:") println(" - Root: ${message.root.tagName}") println(" - Children: ${message.root.children.size}") println(" - Selector: ${message.root.selector}") _domTree.value = message.root _errorMessage.value = null // Clear error on successful update } is WebEditMessage.D2SnapTreeUpdated -> { println("[JvmWebEditBridge] 📦 D2Snap Tree Updated:") println(" - Root: ${message.root.tagName}") println(" - Children: ${message.root.children.size}") _d2SnapTree.value = message.root } is WebEditMessage.AccessibilityTreeUpdated -> { println("[JvmWebEditBridge] ♿ Accessibility Tree Updated:") println(" - Root role: ${message.root.role}") println(" - Root name: ${message.root.name}") _accessibilityTree.value = message.root } is WebEditMessage.ActionableElementsUpdated -> { println("[JvmWebEditBridge] 🎯 Actionable Elements Updated: ${message.elements.size} elements") _actionableElements.value = message.elements } is WebEditMessage.ElementSelected -> { println("[JvmWebEditBridge] ✨ Element Selected: ${message.element.tagName}") _selectedElement.value = message.element } is WebEditMessage.PageLoaded -> { println("[JvmWebEditBridge] 📄 Page Loaded: ${message.title} (${message.url})") _currentUrl.value = message.url _pageTitle.value = message.title _isLoading.value = false _loadProgress.value = 100 _errorMessage.value = null // Clear error on successful page load } is WebEditMessage.Error -> { println("[JvmWebEditBridge] ❌ Error: ${message.message}") _errorMessage.value = message.message } is WebEditMessage.LoadProgress -> { println("[JvmWebEditBridge] ⏳ Load Progress: ${message.progress}%") _loadProgress.value = message.progress } is WebEditMessage.DOMChanged -> { println("[JvmWebEditBridge] 🔄 DOM Changed: ${message.mutationCount} mutations") // DOM changes trigger automatic refresh via MutationObserver } is WebEditMessage.ActionResult -> { _lastActionResult.value = message if (!message.ok) { _errorMessage.value = message.message ?: "Action failed: ${message.action}" } } is WebEditMessage.ScreenshotCaptured -> { println("[JvmWebEditBridge] 📸 Screenshot Captured: ${message.width}x${message.height}, error=${message.error}") _lastScreenshot.value = message if (message.error != null) { _errorMessage.value = message.error } } } } fun setUrl(url: String) { _currentUrl.value = url } fun setTitle(title: String) { _pageTitle.value = title } fun setLoading(loading: Boolean) { _isLoading.value = loading } } actual fun createWebEditBridge(): WebEditBridge = JvmWebEditBridge()