import 'dart:async';
import 'dart:convert';
import 'dart:io';

import 'package:archive/archive_io.dart';
import 'package:jaspr/server.dart';
import 'package:path/path.dart' as path;

import '../models/project.dart';
import 'project.dart';

Future<Response> downloadProject(Request request) async {
  var param = request.url.queryParameters['project']!;
  var data = jsonDecode(utf8.decode(base64Decode(param)));
  var project = ProjectDataMapper.fromMap(data);

  var encoder = ZipProjectEncoder();
  await encoder.zipProject(project);

  return Response.ok(encoder._output.getBytes());
}

class ZipProjectEncoder {
  late OutputStream _output;
  late ZipEncoder _encoder;

  ZipProjectEncoder() {
    _encoder = ZipEncoder();
    _output = OutputStream();
  }

  Future<void> zipProject(ProjectData project) async {
    _encoder.startEncode(_output);

    var replacements = {
      'project_name': project.id?.replaceAll('-', '_') ?? 'jaspr_app',
      'project_description': project.description ?? 'Simple jaspr project',
      'project_title': project.description ?? 'Jaspr',
      'project_html': project.htmlFile?.indent('    ') ?? '',
    };

    await addFromFile('pubspec.yaml', replacements);
    await addFromFile('web/main.dart', replacements);
    await addFromFile('web/index.html', replacements);

    addFromSource('web/styles.css', project.cssFile ?? '');
    addFromSource('lib/main.dart', project.mainDartFile);

    for (var file in project.dartFiles.keys) {
      addFromSource('lib/$file', project.dartFiles[file]!);
    }

    close();
  }

  Future<void> addFromFile(String name, Map<String, String> replacements) async {
    var file = File(path.join(jasprExportTemplatePath, name));
    var source = await file.readAsString();

    for (var key in replacements.keys) {
      source = source.replaceAll('{{$key}}', replacements[key]!);
    }

    addFromSource(name, source);
  }

  void addFromSource(String file, String source) {
    _encoder.addFile(ArchiveFile.string(file, source));
  }

  void close() {
    _encoder.endEncode();
    _output.flush();
  }
}

extension Indent on String {
  String indent(String pad) {
    return split('\n').map((s) => pad + s).join('\n');
  }
}
