import 'dart:convert';

import 'package:scheduler_core/scheduler_core.dart' as core;
import 'package:scheduler_export/export.dart';

/// Create the readable export writer registry used by export commands.
ExportWriterRegistry readableExportWriterRegistry() {
  return ExportWriterRegistry(
    factories: <String, ExportWriterFactory>{
      'json': JsonExportWriter.new,
      'csv': CsvExportWriter.new,
    },
  );
}

/// JSON export writer for scheduler tasks.
final class JsonExportWriter implements ExportWriter {
  /// Creates a JSON writer that writes to the provided string sink.
  JsonExportWriter(this._sink);

  final StringSink _sink;
  ExportContext? _context;
  bool _hasTask = false;
  bool _ended = false;

  @override
  Future<void> begin(ExportContext context) async {
    _assertNotEnded();
    _context = context;
    _sink.write(
      '{"context":${jsonEncode(_contextToJson(context))},"tasks":[',
    );
  }

  @override
  Future<void> writeTask(core.Task task) async {
    final context = _requireContext();
    _assertNotEnded();
    if (_hasTask) {
      _sink.write(',');
    }
    _sink.write(jsonEncode(core.TaskDocumentMapping.toDocument(
      task,
      ownerId: context.ownerId.value,
    )));
    _hasTask = true;
  }

  @override
  Future<void> end() async {
    _requireContext();
    _assertNotEnded();
    _sink.write(']}');
    _ended = true;
  }

  ExportContext _requireContext() {
    final context = _context;
    if (context == null) {
      throw const ExportException('Writer has not started.');
    }
    return context;
  }

  void _assertNotEnded() {
    if (_ended) {
      throw const ExportException('Writer has already ended.');
    }
  }
}

/// CSV export writer for scheduler tasks.
final class CsvExportWriter implements ExportWriter {
  /// Creates a CSV writer that writes to the provided string sink.
  CsvExportWriter(this._sink);

  final StringSink _sink;
  bool _started = false;
  bool _ended = false;

  @override
  Future<void> begin(ExportContext context) async {
    _assertNotEnded();
    _started = true;
    _sink.writeln(
      'id,title,projectId,type,status,durationMinutes,scheduledStartUtc,'
      'scheduledEndUtc',
    );
  }

  @override
  Future<void> writeTask(core.Task task) async {
    if (!_started) {
      throw const ExportException('Writer has not started.');
    }
    _assertNotEnded();
    _sink.writeln(<String>[
      task.id,
      task.title,
      task.projectId,
      core.PersistenceEnumMapping.encodeTaskType(task.type),
      core.PersistenceEnumMapping.encodeTaskStatus(task.status),
      task.durationMinutes?.toString() ?? '',
      task.scheduledStart == null
          ? ''
          : core.PersistenceDateTimeConvention.toStoredString(
              task.scheduledStart!,
            ),
      task.scheduledEnd == null
          ? ''
          : core.PersistenceDateTimeConvention.toStoredString(
              task.scheduledEnd!,
            ),
    ].map(_csvCell).join(','));
  }

  @override
  Future<void> end() async {
    if (!_started) {
      throw const ExportException('Writer has not started.');
    }
    _assertNotEnded();
    _ended = true;
  }

  void _assertNotEnded() {
    if (_ended) {
      throw const ExportException('Writer has already ended.');
    }
  }
}

Map<String, Object?> _contextToJson(ExportContext context) {
  return <String, Object?>{
    'ownerId': context.ownerId.value,
    'timezone': context.timezone,
    'version': context.version,
  };
}

String _csvCell(String value) {
  final needsQuotes =
      value.contains(',') || value.contains('"') || value.contains('\n');
  final escaped = value.replaceAll('"', '""');
  return needsQuotes ? '"$escaped"' : escaped;
}
