// ignore_for_file: avoid_print

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

const packages = ['backend', 'frontend', 'shared_models'];
Map<int, Process> runnerWatchProcesses = {};

void main(List<String> args) async {
  // Get args and create bool for if `--watch` passed in
  final watch = args.contains('--watch');

  if (watch) {
    print('👀 Running build_runner in watch mode for all packages...\n');
  } else {
    print('🏃 Running build_runner in build mode for all packages...\n');
  }

  final projectRootPath = await Process.run(
    'git',
    ['rev-parse', '--show-toplevel'],
  ).then((result) => result.stdout.toString().trim());

  for (final package in packages) {
    unawaited(runBuildRunner('$projectRootPath/$package', watch: watch));
  }
  await Future<void>.delayed(const Duration(seconds: 1));

  var i = 0;
  var j = 0;
  while (runnerWatchProcesses.isNotEmpty) {
    await Future<void>.delayed(const Duration(seconds: 1));
    i++;

    if (i == 10) {
      if (!watch) print('   Waiting for build to finish... ${runnerWatchProcesses.keys}');
      if (packages.length != runnerWatchProcesses.entries.length) {
        j++;
      } else {
        j = 0;
      }
      if (j == 2) {
        print(
          'Missing build runners for too long in tracked processes. Something probably went wrong. Check logs and fix the script.',
        );
        exitRunners();
      }
    }
  }

  print('\n✅ All build_runner tasks completed successfully!');
}

Future<void> runBuildRunner(String packagePath, {required bool watch}) async {
  final package = packagePath.split('/').last;
  print('📦 Starting build_runner for $package...');
  Future<(Process, bool, String)> buildRunnerFunc() =>
      watch ? _runBuildRunnerWatch(packagePath) : _runBuildRunner(packagePath);
  const maxAttempts = 5;

  Future<void> attemptRecover(Object? e, int i) async {
    try {
      print('\n⚠️  Error in $package:\n$e🤞 Attempting recovery (${i + 1} / $maxAttempts)...');

      print('   Waiting 10 seconds before retry...');
      await Future<void>.delayed(const Duration(seconds: 10));

      // Recovery attempt
      final cmd = package == 'frontend' ? 'flutter' : 'dart';
      print('📥 Running $cmd pub get...');

      await Process.run(
        cmd,
        ['pub', 'get'],
        workingDirectory: packagePath,
      );

      print('   Retrying build_runner...');
      return;
    } catch (e) {
      print('Error while trying to recover $package: $e');
    }
  }

  Object? err;
  for (var i = 0; i < maxAttempts; i++) {
    try {
      final (process, needsRestart, errLogs) = await buildRunnerFunc();
      final removed = runnerWatchProcesses.remove(process.pid);
      assert(removed != null, true);
      if (needsRestart) {
        await attemptRecover(errLogs, i);
      } else {
        return;
      }
    } catch (e) {
      await attemptRecover(e, i);
    }
  }
  print('\n❌ Failed to recover $packagePath: $err');
  exitRunners();
}

Future<(Process, bool, String)> _runBuildRunner(String package) async {
  late final Process process;
  if (package == 'frontend') {
    process = await Process.start(
      'flutter',
      ['pub', 'run', 'build_runner', 'build', '--delete-conflicting-outputs'],
      workingDirectory: package,
    );
  } else {
    process = await Process.start(
      'dart',
      ['run', 'build_runner', 'build', '--delete-conflicting-outputs'],
      workingDirectory: package,
    );
  }

  runnerWatchProcesses.addAll({process.pid: process});

  final outputLines = <String>[];
  final errorLines = <String>[];

  process.stdout.transform(const SystemEncoding().decoder).listen(outputLines.add);

  process.stderr.transform(const SystemEncoding().decoder).listen(errorLines.add);

  final exitCode = await process.exitCode;

  if (exitCode != 0) {
    // Print filtered error output
    final relevantErrors = errorLines
        .where((line) => line.contains('ERROR') || line.contains('Exception') || line.contains('Failed'))
        .join('\n');

    print('Build runner failed for $package:\n$relevantErrors');
    return (process, true, '');
  }

  // Print success with minimal output
  final successMessage =
      outputLines.where((line) => line.contains('Succeeded') || line.contains('Generated')).join('\n').trim();

  print('   ${successMessage.isEmpty ? "Completed successfully" : successMessage}');
  return (process, false, '');
}

Future<(Process, bool, String)> _runBuildRunnerWatch(String packagePath) async {
  final package = packagePath.split('/').last;
  late final Process process;
  if (package == 'frontend') {
    process = await Process.start(
      'flutter',
      ['pub', 'run', 'build_runner', 'watch', '--delete-conflicting-outputs'],
      workingDirectory: packagePath,
    );
  } else {
    process = await Process.start(
      'dart',
      ['run', 'build_runner', 'watch', '--delete-conflicting-outputs'],
      workingDirectory: packagePath,
    );
  }
  final logs = <String>[];

  runnerWatchProcesses.addAll({process.pid: process});

  var restart = false;
  process.stdout.transform(const SystemEncoding().decoder).listen((line) {
    if (line.contains('Succeeded') || line.contains('Generated')) {
      print('🎯 [${package.toUpperCase()}] - ${line.trim()}');
    }
    if (line.contains('SEVERE')) {
      print('🚩 [${package.toUpperCase()}] - ${line.trim()}');
      restart = true;
    }
    logs.add(line);
  });

  process.stderr.transform(const SystemEncoding().decoder).listen((line) {
    if (line.contains('ERROR') || line.contains('Exception') || line.contains('Failed')) {
      print('🎯 [${package.toUpperCase()}] - ${line.trim()}');
      restart = true;
    }
    logs.add(line);
  });

  final code = await process.exitCode;
  if (!restart) {
    restart = code != 0;
  }
  return (process, restart, logs.join('\n'));
}

void exitRunners() {
  print('❌ Killing processes');
  for (final pEntry in runnerWatchProcesses.entries) {
    pEntry.value.kill();
  }
  exit(1);
}