import 'package:flutter_riverpod/flutter_riverpod.dart';

import '../game/engine/game_engine.dart';
import '../game/engine/piece_generator.dart';
import '../game/models/grid.dart';
import '../game/models/objective.dart';
import '../game/models/piece.dart';
import '../game/models/stage.dart';
import 'providers.dart';

/// Immutable snapshot of one engine moment; the only game state the UI sees.
class GameViewState {
  const GameViewState({
    required this.grid,
    required this.tray,
    required this.score,
    required this.comboStreak,
    required this.movesLeft,
    required this.moveLimit,
    required this.phase,
    required this.stuckReason,
    required this.objectives,
    required this.starsEarned,
    required this.objectiveProgress,
    required this.lastPlacement,
    required this.fxTick,
    required this.endless,
    required this.rescueUsed,
  });

  final GridState grid;
  final List<Piece> tray;
  final int score;
  final int comboStreak;
  final int movesLeft;
  final int moveLimit;
  final GamePhase phase;
  final StuckReason? stuckReason;
  final List<Objective> objectives;
  final int starsEarned;
  final double objectiveProgress;
  final PlacementResult? lastPlacement;

  /// Increments on every accepted placement so animations can retrigger.
  final int fxTick;

  final bool endless;
  final bool rescueUsed;
}

/// Bridges the pure-Dart [GameEngine] to the widget tree. The engine object
/// never leaks past this class.
class GameSessionNotifier extends Notifier<GameViewState?> {
  GameEngine? _engine;
  StageConfig? _stage;
  PieceGenerator? _generatorOverride;
  int _attempt = 0;
  int _fxTick = 0;

  @override
  GameViewState? build() => null;

  void startStage(StageConfig stage,
      {int attempt = 0, PieceGenerator? generator}) {
    _stage = stage;
    _attempt = attempt;
    _generatorOverride = generator;
    _engine = GameEngine(stage, attempt: attempt, generator: generator);
    _fxTick = 0;
    _publish(lastPlacement: null);
    ref.read(adServiceProvider).onRoundStart();
  }

  /// Restarts the current stage as a new attempt (fresh piece sequence).
  void restart() {
    final stage = _stage;
    if (stage == null) throw StateError('no stage to restart');
    startStage(stage, attempt: _attempt + 1, generator: _generatorOverride);
    if (stage.endless) {
      ref.read(analyticsProvider).endlessStart();
    } else {
      final flow = ref.read(seasonFlowProvider);
      if (flow != null) {
        ref.read(analyticsProvider).stageStart(
              seasonId: flow.pack.seasonId,
              stageId: flow.stage.id,
            );
      }
    }
  }

  bool tryPlace(int trayIndex, int x, int y) {
    final engine = _engine;
    if (engine == null) return false;
    final result = engine.tryPlace(trayIndex, x, y);
    if (result == null) return false;
    _fxTick++;
    _publish(lastPlacement: result);
    return true;
  }

  bool canPlaceAt(int trayIndex, int x, int y) =>
      _engine?.tryPlaceWouldSucceed(trayIndex, x, y) ?? false;

  void useContinue() {
    _engine?.useContinue();
    _publish(lastPlacement: null);
  }

  void addExtraMoves() {
    _engine?.addExtraMoves();
    _publish(lastPlacement: null);
  }

  void declineAndLose() {
    _engine?.declineAndLose();
    _publish(lastPlacement: null);
  }

  void _publish({required PlacementResult? lastPlacement}) {
    final engine = _engine!;
    state = GameViewState(
      grid: engine.grid,
      tray: engine.tray,
      score: engine.score,
      comboStreak: engine.combo.streak,
      movesLeft: engine.movesLeft,
      endless: engine.endless,
      moveLimit: engine.endless ? 0 : engine.movesLeft + engine.movesUsed,
      phase: engine.phase,
      stuckReason: engine.stuckReason,
      objectives: engine.objectives,
      starsEarned: engine.starsEarned,
      objectiveProgress: engine.objectiveProgress,
      lastPlacement: lastPlacement,
      fxTick: _fxTick,
      rescueUsed: engine.rescueUsed,
    );
  }
}
