import '../../core/rng.dart';
import '../models/cell.dart';
import '../models/grid.dart';
import '../models/stage.dart';
import 'game_engine.dart';
import 'line_clear.dart';
import 'piece_generator.dart';
import 'placement.dart';
import 'scoring.dart';

class BotRun {
  const BotRun({
    required this.won,
    required this.movesUsed,
    required this.movesLeft,
    required this.stars,
    required this.score,
  });

  final bool won;
  final int movesUsed;
  final int movesLeft;
  final int stars;
  final int score;
}

/// Greedy objective-seeking bot used by the stage generator to calibrate
/// difficulty (win rates, move budgets) against the real shipping engine.
/// It never uses rescues, so its results reflect a clean attempt.
class AutoPlayer {
  BotRun play(StageConfig stage, {int attempt = 0}) {
    final engine = GameEngine(stage, attempt: attempt);
    final tieBreaker = SeededRng(stage.seed ^ (attempt * 31 + 7));
    var guard = 0;

    while (engine.phase == GamePhase.playing && guard < 400) {
      guard++;
      final moves = <(double, int, int, int)>[];

      for (var i = 0; i < engine.tray.length; i++) {
        final piece = engine.tray[i];
        for (var y = 0; y < GridState.size; y++) {
          for (var x = 0; x < GridState.size; x++) {
            if (!canPlace(engine.grid, piece, x, y)) continue;
            final clear = detectAndClear(place(engine.grid, piece, x, y));
            final h = clear.gemsCleared * 120.0 +
                lineClearBase(clear.linesCleared) * 0.5 +
                piece.size -
                clear.grid.fillRatio * 10 +
                _gemLineProgress(clear.grid) * 30 +
                tieBreaker.nextDouble() * 1e-6;
            moves.add((h, i, x, y));
          }
        }
      }

      if (moves.isEmpty) break;
      moves.sort((a, b) => b.$1.compareTo(a.$1));

      // Greedy alone walls itself in: prefer the best-ranked move that
      // leaves the rest of the tray playable (with clears resolved).
      var chosen = moves.first;
      for (final move in moves.take(10)) {
        final (_, i, x, y) = move;
        final after =
            detectAndClear(place(engine.grid, engine.tray[i], x, y)).grid;
        final rest = [
          for (var k = 0; k < engine.tray.length; k++)
            if (k != i) engine.tray[k],
        ];
        if (isTrayPlayable(after, rest)) {
          chosen = move;
          break;
        }
      }
      engine.tryPlace(chosen.$2, chosen.$3, chosen.$4);
    }

    return BotRun(
      won: engine.phase == GamePhase.won,
      movesUsed: engine.movesUsed,
      movesLeft: engine.movesLeft,
      stars: engine.starsEarned,
      score: engine.score,
    );
  }

  /// How close remaining gems are to being cleared: for each gem, the best
  /// fill fraction of its row or column. Steers the bot toward building the
  /// lines that matter.
  double _gemLineProgress(GridState grid) {
    var progress = 0.0;
    for (var y = 0; y < GridState.size; y++) {
      for (var x = 0; x < GridState.size; x++) {
        if (grid.cellAt(x, y).type != CellType.gem) continue;
        var rowFill = 0;
        var colFill = 0;
        for (var i = 0; i < GridState.size; i++) {
          if (grid.isOccupied(i, y)) rowFill++;
          if (grid.isOccupied(x, i)) colFill++;
        }
        progress += (rowFill > colFill ? rowFill : colFill) / GridState.size;
      }
    }
    return progress;
  }
}
