import 'dart:async' as async; import '../diagnostics/runtime_diagnostics.dart'; import '../models/runtime_event.dart'; typedef RuntimeHostCallHandler = async.FutureOr Function(RuntimeHostCall call); typedef RuntimeHostNotifyHandler = void Function(RuntimeHostNotification notification); class RuntimeHostBridge { const RuntimeHostBridge({this.handlers = const {}, this.onNotify}); final Map handlers; final RuntimeHostNotifyHandler? onNotify; } class RuntimeHostCall { const RuntimeHostCall({required this.id, required this.method, this.data}); final String id; final String method; final Object? data; } class RuntimeHostNotification { const RuntimeHostNotification({required this.method, this.data}); final String method; final Object? data; } class RuntimeHostBridgeManager { RuntimeHostBridgeManager({ required RuntimeHostBridge bridge, required void Function(RuntimeEvent event) eventSink, RuntimeDiagnostics? diagnostics, }) : _bridge = bridge, _eventSink = eventSink, _diagnostics = diagnostics; final RuntimeHostBridge _bridge; final void Function(RuntimeEvent event) _eventSink; final RuntimeDiagnostics? _diagnostics; final Map> _pendingLuaCalls = {}; var _nextCallId = 0; bool _disposed = false; Future callHost(RuntimeHostCall call) async { if (_disposed) { return; } final handler = _bridge.handlers[call.method]; if (handler == null) { _emitHostCallResult( id: call.id, method: call.method, ok: false, error: 'No host handler registered for ${call.method}', ); return; } try { final result = await handler(call); _emitHostCallResult( id: call.id, method: call.method, ok: true, result: result, ); } catch (error) { _diagnostics?.record( type: RuntimeDiagnosticType.hostBridgeError, message: 'Runtime host call failed', error: error, context: {'id': call.id, 'method': call.method}, ); _emitHostCallResult( id: call.id, method: call.method, ok: false, error: error.toString(), ); } } bool notifyHost(RuntimeHostNotification notification) { if (_disposed) { return false; } final handler = _bridge.onNotify; if (handler == null) { return false; } try { handler(notification); return true; } catch (error) { _diagnostics?.record( type: RuntimeDiagnosticType.hostBridgeError, message: 'Runtime host notification failed', error: error, context: {'method': notification.method}, ); return false; } } Future callLua( String method, { Object? data, Duration timeout = const Duration(seconds: 15), }) { if (_disposed) { return Future.error(StateError('Runtime host bridge disposed')); } final id = 'host:${++_nextCallId}'; final completer = async.Completer(); _pendingLuaCalls[id] = completer; _emit( RuntimeEvent( type: RuntimeHostEventType.call, data: { 'id': id, 'method': method, if (data != null) 'data': _runtimeValue(data), }, ), ); return completer.future.timeout( timeout, onTimeout: () { _pendingLuaCalls.remove(id); throw async.TimeoutException( 'Lua host call timed out: $method', timeout, ); }, ); } bool notifyLua(String method, {Object? data}) { if (_disposed) { return false; } _emit( RuntimeEvent( type: RuntimeHostEventType.notify, data: {'method': method, if (data != null) 'data': _runtimeValue(data)}, ), ); return true; } bool completeLuaCall(String id, {Object? result, String? error}) { final completer = _pendingLuaCalls.remove(id); if (completer == null || completer.isCompleted) { return false; } if (error != null) { completer.completeError(StateError(error)); } else { completer.complete(_runtimeValue(result)); } return true; } void dispose() { _disposed = true; for (final completer in _pendingLuaCalls.values) { if (!completer.isCompleted) { completer.completeError(StateError('Runtime host bridge disposed')); } } _pendingLuaCalls.clear(); } void _emitHostCallResult({ required String id, required String method, required bool ok, Object? result, String? error, }) { _emit( RuntimeEvent( type: RuntimeHostEventType.callResult, data: { 'id': id, 'method': method, 'ok': ok, if (ok) 'result': _runtimeValue(result), if (!ok && error != null) 'error': error, }, ), ); } void _emit(RuntimeEvent event) { if (_disposed) { return; } _eventSink(event); } Object? _runtimeValue(Object? value) { if (value == null || value is String || value is num || value is bool) { return value; } if (value is Iterable) { return value.map(_runtimeValue).toList(growable: false); } if (value is Map) { return { for (final entry in value.entries) entry.key.toString(): _runtimeValue(entry.value), }; } return value.toString(); } } abstract final class RuntimeHostEventType { static const notify = 'host_notify'; static const call = 'host_call'; static const callResult = 'host_call_result'; }