class PlateLoadResult {
  final bool success;
  final List<double> plateConfiguration;
  final String? bandAssistance;
  final double totalAchieved;
  final String message;

  PlateLoadResult({
    required this.success,
    required this.plateConfiguration,
    this.bandAssistance,
    required this.totalAchieved,
    this.message = '',
  });
}

class PlateCalculator {
  /// Calculate plate loading for a target weight
  ///
  /// [targetWeight]: Total weight to achieve
  /// [barWeight]: Weight of the bar (or Bodyweight for calisthenics)
  /// [availablePlates]: List of available plate weights
  /// [availableBands]: Map of Band Name -> Resistance in KG
  /// [isTwoSided]: true for barbell, false for dip belt
  static PlateLoadResult calculate({
    required double targetWeight,
    required double barWeight,
    required List<double> availablePlates,
    Map<String, double> availableBands = const {},
    required bool isTwoSided,
  }) {
    double needed = targetWeight - barWeight;

    if (needed < 0 && !isTwoSided) {
      final deficit = needed.abs();
      String? bestBand;
      double closestResistance = 0.0;
      double minDiff = double.infinity;

      availableBands.forEach((name, resistance) {
        final diff = (resistance - deficit).abs();
        if (diff < minDiff) {
          minDiff = diff;
          bestBand = name;
          closestResistance = resistance;
        }
      });

      // SMART FALLBACK LOGIK:
      // Prüfen, ob "Kein Band" (Bodyweight) näher am Ziel liegt als das "Beste Band".
      // Beispiel: Benötigt 2kg Support. Kleinstes Band 10kg.
      // - Fehler mit Band: |10 - 2| = 8kg zu viel Hilfe.
      // - Fehler ohne Band: 2kg zu wenig Hilfe.
      // -> Da 2kg < 8kg, ist Bodyweight präziser (und trainingstechnisch besser als viel zu leicht).

      final deviationWithBand = minDiff; // Abweichung bei Band-Nutzung
      final deviationWithBW = deficit; // Abweichung bei Bodyweight (0 Support)

      // Wir erlauben Bodyweight auch, wenn der Defizit extrem klein ist (< 1kg),
      // da Bänder selten so fein abgestuft sind.
      if (bestBand == null ||
          deviationWithBW <= deviationWithBand ||
          deficit < 1.0) {
        return PlateLoadResult(
          success: true,
          plateConfiguration: [],
          totalAchieved: barWeight,
          message: 'Bodyweight Only (Closer to target than band)',
        );
      }

      return PlateLoadResult(
        success: true,
        plateConfiguration: [],
        bandAssistance: bestBand,
        totalAchieved: barWeight - closestResistance,
        message: 'Use $bestBand band for assistance',
      );
    }

    if (needed <= 0.1) {
      return PlateLoadResult(
        success: true,
        plateConfiguration: [],
        totalAchieved: barWeight,
        message: isTwoSided ? 'Bar only' : 'Bodyweight Only',
      );
    }

    final sortedPlates = List<double>.from(availablePlates)
      ..sort((a, b) => b.compareTo(a));

    if (sortedPlates.isEmpty) {
      return PlateLoadResult(
        success: false,
        plateConfiguration: [],
        totalAchieved: barWeight,
        message: 'No plates available',
      );
    }

    final smallestPlate = sortedPlates.last;
    final targetPerSideRaw = isTwoSided ? needed / 2 : needed;

    final roundedPerSide =
        (targetPerSideRaw / smallestPlate).round() * smallestPlate;

    if (roundedPerSide <= 0.001) {
      return PlateLoadResult(
        success: true,
        plateConfiguration: [],
        totalAchieved: barWeight,
        message: isTwoSided ? 'Bar only' : 'Bodyweight Only',
      );
    }

    final result = _greedyFit(roundedPerSide, sortedPlates);

    if (!result.success) {
      return PlateLoadResult(
        success: false,
        plateConfiguration: [],
        totalAchieved: barWeight,
        message: 'Cannot achieve target with available plates',
      );
    }

    final totalLoaded = isTwoSided
        ? barWeight + (result.totalWeight * 2)
        : barWeight + result.totalWeight;

    return PlateLoadResult(
      success: true,
      plateConfiguration: result.plates,
      totalAchieved: totalLoaded,
    );
  }

  static _FitResult _greedyFit(double target, List<double> plates) {
    final loaded = <double>[];
    var remaining = target;
    const epsilon = 0.01;

    for (final plate in plates) {
      while (remaining >= plate - epsilon) {
        loaded.add(plate);
        remaining -= plate;
      }
    }

    final success = remaining.abs() < epsilon;
    return _FitResult(
      success: success,
      plates: loaded,
      totalWeight: loaded.fold(0.0, (sum, p) => sum + p),
    );
  }
}

class _FitResult {
  final bool success;
  final List<double> plates;
  final double totalWeight;

  _FitResult({
    required this.success,
    required this.plates,
    required this.totalWeight,
  });
}
