import 'dart:async'; import '../layout/layout_controller.dart'; import '../session/log_buffer.dart'; import '../session/packet_buffer.dart'; typedef ProgressCallback = void Function(double progress); /// Returned by export operations so the UI can report what happened. class ExportResult { ExportResult({required this.path, required this.bytesWritten}); /// On native: filesystem path. On Web: filename. final String path; final int bytesWritten; } /// Builds CSV row strings. Shared by both platform implementations so the /// row layout is defined exactly once. class CsvRowBuilder { CsvRowBuilder(this.layout); final LayoutController layout; /// Header row for data CSV. Disabled channels are omitted; pause and the /// 8 status fields always appear. String dataHeader() { final cols = ['timestamp_us']; for (var ch = 1; ch <= 16; ch++) { if (layout.configFor(ch).enabled) { cols.add('ch${ch}_${_safe(layout.configFor(ch).name)}'); } } cols.add('pause'); for (var s = 0; s < 8; s++) { cols.add('status${s + 1}_${_safe(layout.statusNames[s])}'); } return cols.join(','); } /// Row for one packet. Missing values are blank cells. String dataRow(packet) { final cells = []; cells.add(packet.hasTimestampUs() ? packet.timestampUs.toString() : ''); for (var ch = 1; ch <= 16; ch++) { if (!layout.configFor(ch).enabled) continue; cells.add(_channelCell(packet, ch)); } cells.add(packet.hasPause() ? (packet.pause ? '1' : '0') : ''); for (var s = 1; s <= 8; s++) { cells.add(_statusCell(packet, s)); } return cells.join(','); } String logHeader() => 'timestamp_us,severity,error_number,description'; String logRow(entry) { final ts = entry.hasTimestampUs() ? entry.timestampUs.toString() : ''; final sev = entry.hasSeverity() ? entry.severity.name : ''; final err = entry.hasErrorNumber() ? entry.errorNumber.toString() : ''; final desc = entry.hasDescription() ? _escape(entry.description) : ''; return '$ts,$sev,$err,$desc'; } static String _channelCell(packet, int ch) { switch (ch) { case 1: return packet.hasCh1() ? packet.ch1.toString() : ''; case 2: return packet.hasCh2() ? packet.ch2.toString() : ''; case 3: return packet.hasCh3() ? packet.ch3.toString() : ''; case 4: return packet.hasCh4() ? packet.ch4.toString() : ''; case 5: return packet.hasCh5() ? packet.ch5.toString() : ''; case 6: return packet.hasCh6() ? packet.ch6.toString() : ''; case 7: return packet.hasCh7() ? packet.ch7.toString() : ''; case 8: return packet.hasCh8() ? packet.ch8.toString() : ''; case 9: return packet.hasCh9() ? packet.ch9.toString() : ''; case 10: return packet.hasCh10() ? packet.ch10.toString() : ''; case 11: return packet.hasCh11() ? packet.ch11.toString() : ''; case 12: return packet.hasCh12() ? packet.ch12.toString() : ''; case 13: return packet.hasCh13() ? packet.ch13.toString() : ''; case 14: return packet.hasCh14() ? packet.ch14.toString() : ''; case 15: return packet.hasCh15() ? packet.ch15.toString() : ''; case 16: return packet.hasCh16() ? packet.ch16.toString() : ''; default: return ''; } } static String _statusCell(packet, int s) { switch (s) { case 1: return packet.hasStatus1() ? packet.status1.toString() : ''; case 2: return packet.hasStatus2() ? packet.status2.toString() : ''; case 3: return packet.hasStatus3() ? packet.status3.toString() : ''; case 4: return packet.hasStatus4() ? packet.status4.toString() : ''; case 5: return packet.hasStatus5() ? packet.status5.toString() : ''; case 6: return packet.hasStatus6() ? packet.status6.toString() : ''; case 7: return packet.hasStatus7() ? packet.status7.toString() : ''; case 8: return packet.hasStatus8() ? packet.status8.toString() : ''; default: return ''; } } /// Strip characters that would break CSV column names. static String _safe(String s) => s.replaceAll(RegExp(r'[,\s]+'), '_'); /// Escape a free-form field for CSV. Wraps in quotes if needed. static String _escape(String s) { if (s.contains(',') || s.contains('"') || s.contains('\n')) { return '"${s.replaceAll('"', '""')}"'; } return s; } } /// Common interface implemented by both platform exporters. abstract class CsvExporter { /// Export the entire packet buffer as data CSV. Future exportData({ required PacketBuffer buffer, required LayoutController layout, ProgressCallback? onProgress, }); /// Export the entire log buffer as log CSV. Future exportLog({ required LogBuffer buffer, ProgressCallback? onProgress, }); }