import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:uuid/uuid.dart'; import 'package:apidash_core/apidash_core.dart'; import '../models/terminal_models.dart'; final terminalStateProvider = StateNotifierProvider((ref) { return TerminalController(); }); class TerminalState { TerminalState({required this.entries}) : index = { for (var i = 0; i < entries.length; i++) entries[i].id: i, }; final List entries; // newest first final Map index; TerminalState copyWith({List? entries}) { return TerminalState(entries: entries ?? this.entries); } } class TerminalController extends StateNotifier { TerminalController() : super(TerminalState(entries: [])); static const _uuid = Uuid(); String _newId() => _uuid.v4(); void clear() { state = TerminalState(entries: []); } String append(TerminalEntry entry) { final list = [entry, ...state.entries]; state = TerminalState(entries: list); return entry.id; } void _updateById(String id, TerminalEntry Function(TerminalEntry) updater) { final idx = state.index[id]; if (idx == null) return; final current = state.entries[idx]; final updated = updater(current); final list = [...state.entries]; list[idx] = updated; state = TerminalState(entries: list); } // Convenience builders String startNetwork({ required APIType apiType, required HTTPVerb method, required String url, String? requestId, Map? requestHeaders, String? requestBodyPreview, bool isStreaming = false, }) { final id = _newId(); final entry = TerminalEntry( id: id, ts: DateTime.now(), source: TerminalSource.network, level: TerminalLevel.info, requestId: requestId, network: NetworkLogData( phase: NetworkPhase.started, apiType: apiType, method: method, url: url, requestHeaders: requestHeaders, requestBodyPreview: requestBodyPreview, isStreaming: isStreaming, sentAt: DateTime.now(), ), ); append(entry); return id; } void addNetworkChunk(String logId, BodyChunk chunk) { _updateById(logId, (e) { final n = e.network; if (n == null) return e; n.chunks.add(chunk); return e.copyWith( network: n.copyWith(phase: NetworkPhase.progress), ); }); } void completeNetwork( String logId, { int? statusCode, Map? responseHeaders, String? responseBodyPreview, Duration? duration, }) { _updateById(logId, (e) { final n = e.network; if (n == null) return e; return e.copyWith( level: (statusCode != null && statusCode >= 400) ? TerminalLevel.error : TerminalLevel.info, network: n.copyWith( phase: NetworkPhase.completed, responseStatus: statusCode, responseHeaders: responseHeaders, responseBodyPreview: responseBodyPreview, duration: duration, completedAt: DateTime.now(), ), ); }); } void failNetwork(String logId, String message) { _updateById(logId, (e) { final n = e.network; if (n == null) return e; return e.copyWith( level: TerminalLevel.error, network: n.copyWith( phase: NetworkPhase.failed, errorMessage: message, completedAt: DateTime.now(), ), ); }); } void logJs({ required String level, required List args, String? stack, String? context, String? contextRequestId, }) { append(TerminalEntry( id: _newId(), ts: DateTime.now(), source: TerminalSource.js, level: switch (level) { 'warn' => TerminalLevel.warn, 'error' || 'fatal' => TerminalLevel.error, _ => TerminalLevel.info, }, requestId: contextRequestId, js: JsLogData( level: level, args: args, stack: stack, context: context, contextRequestId: contextRequestId, ), )); } void logSystem({ required String category, required String message, String? stack, TerminalLevel level = TerminalLevel.info, List tags = const [], }) { append(TerminalEntry( id: _newId(), ts: DateTime.now(), source: TerminalSource.system, level: level, tags: tags, system: SystemLogData(category: category, message: message, stack: stack), )); } }