// ===========================================================================
// FILE: lib/models/game_board.dart
// ===========================================================================

import 'dart:math';

enum Player { red, blue, none }
enum BoxType { normal, gold, bomb, invisible, swap, ice, multiplier }
// --- AGGIUNTA LA FORMA 3D ---
enum ArenaShape { classic, cross, donut, hourglass, chaos, test, pyramid3D }

class Dot {
  final int x;
  final int y;
  final int z; // --- NUOVO: ALTEZZA 3D ---
  Dot(this.x, this.y, {this.z = 0});

  @override
  bool operator ==(Object other) => identical(this, other) || other is Dot && runtimeType == other.runtimeType && x == other.x && y == other.y && z == other.z;
  @override
  int get hashCode => x.hashCode ^ y.hashCode ^ z.hashCode;
}

class Line {
  final Dot p1;
  final Dot p2;
  Player owner = Player.none;
  bool isPlayable = false;
  bool isIceCracked = false;

  Line(this.p1, this.p2);

  bool connects(Dot a, Dot b) { return (p1 == a && p2 == b) || (p1 == b && p2 == a); }
}

class Box {
  final int x;
  final int y;
  final int z; // --- NUOVO: PIANO DELLA SCATOLA ---
  Player owner = Player.none;
  late Line top, bottom, left, right;
  BoxType type = BoxType.normal;

  bool isRevealed = false;
  Player? hiddenJokerOwner;
  bool isJokerRevealed = false;

  Box(this.x, this.y, {this.z = 0});

  bool isClosed() {
    if (type == BoxType.invisible) return false;
    return top.owner != Player.none && bottom.owner != Player.none && left.owner != Player.none && right.owner != Player.none;
  }

  int getCalculatedValue(Player closer) {
    if (hiddenJokerOwner != null) return (closer == hiddenJokerOwner) ? 2 : -1;
    if (type == BoxType.gold) return 2;
    if (type == BoxType.bomb) return -1;
    if (type == BoxType.swap || type == BoxType.ice || type == BoxType.multiplier) return 0;

    // --- NUOVO: NELLA PIRAMIDE I PIANI ALTI VALGONO DI PIÙ! ---
    return 1 + z;
  }
}

class GameBoard {
  final int radius;
  final int level;
  final int? seed;
  final ArenaShape shape;

  late int columns;
  late int rows;

  List<Dot> dots = [];
  List<Line> lines = [];
  List<Box> boxes = [];

  Player currentPlayer = Player.red;
  int scoreRed = 0;
  int scoreBlue = 0;
  bool isGameOver = false;
  Line? lastMove;
  bool redHasMultiplier = false;
  bool blueHasMultiplier = false;

  GameBoard({required this.radius, this.level = 1, this.seed, this.shape = ArenaShape.classic}) {
    _generateBoard();
  }

  void _generateBoard() {
    dots.clear(); lines.clear(); boxes.clear(); lastMove = null;

    if (shape == ArenaShape.pyramid3D) {
      // --- LOGICA GENERAZIONE PIRAMIDE 3D ---
      columns = 4; rows = 4; // Base 4x4
      int maxZ = 4; // 4 Piani (0, 1, 2, 3)

      for (int z = 0; z < maxZ; z++) {
        int currentLayerSize = columns - z; // Il piano si restringe man mano che sale
        for (int y = 0; y < currentLayerSize; y++) {
          for (int x = 0; x < currentLayerSize; x++) {
            var box = Box(x, y, z: z);
            // Più sali, più possibilità ci sono di trovare Oro o Bombe
            if (z > 0 && Random().nextDouble() > 0.8) box.type = BoxType.gold;
            if (z > 1 && Random().nextDouble() > 0.85) box.type = BoxType.bomb;
            boxes.add(box);

            Dot tl = _getOrAddDot(x, y, z);
            Dot tr = _getOrAddDot(x + 1, y, z);
            Dot bl = _getOrAddDot(x, y + 1, z);
            Dot br = _getOrAddDot(x + 1, y + 1, z);

            box.top = _getOrAddLine(tl, tr); box.bottom = _getOrAddLine(bl, br);
            box.left = _getOrAddLine(tl, bl); box.right = _getOrAddLine(tr, br);
          }
        }
      }
      _updatePyramidPlayability(); // Blocchiamo i piani superiori!
    } else {
      // (Qui c'è il codice standard 2D che abbiamo lasciato invariato per non rompere nulla)
      columns = shape == ArenaShape.chaos ? radius * 2 + 1 : (shape == ArenaShape.test ? 3 : radius * 2 + 1);
      rows = shape == ArenaShape.chaos ? (radius * 3) + 2 : (shape == ArenaShape.test ? 3 : radius * 2 + 1);

      for (int y = 0; y < rows; y++) {
        for (int x = 0; x < columns; x++) {
          var box = Box(x, y);
          boxes.add(box);
          Dot tl = _getOrAddDot(x, y, 0); Dot tr = _getOrAddDot(x + 1, y, 0);
          Dot bl = _getOrAddDot(x, y + 1, 0); Dot br = _getOrAddDot(x + 1, y + 1, 0);
          box.top = _getOrAddLine(tl, tr); box.bottom = _getOrAddLine(bl, br);
          box.left = _getOrAddLine(tl, bl); box.right = _getOrAddLine(tr, br);
          box.top.isPlayable = true; box.bottom.isPlayable = true; box.left.isPlayable = true; box.right.isPlayable = true;
        }
      }
    }
  }

  // Sblocca i piani superiori solo se il "pavimento" è solido
  void _updatePyramidPlayability() {
    if (shape != ArenaShape.pyramid3D) return;
    for (var box in boxes) {
      if (box.z == 0) {
        if (box.owner == Player.none) {
          box.top.isPlayable = true; box.bottom.isPlayable = true; box.left.isPlayable = true; box.right.isPlayable = true;
        }
      } else {
        // Cerca i 4 quadrati del piano di sotto che lo sorreggono
        bool isSupported = true;
        for (int dy = 0; dy <= 1; dy++) {
          for (int dx = 0; dx <= 1; dx++) {
            var supportBox = boxes.where((b) => b.z == box.z - 1 && b.x == box.x + dx && b.y == box.y + dy).firstOrNull;
            if (supportBox == null || !supportBox.isClosed()) isSupported = false;
          }
        }
        if (box.owner == Player.none) {
          box.top.isPlayable = isSupported; box.bottom.isPlayable = isSupported;
          box.left.isPlayable = isSupported; box.right.isPlayable = isSupported;
        }
      }
    }
  }

  Dot _getOrAddDot(int x, int y, int z) {
    for (var dot in dots) { if (dot.x == x && dot.y == y && dot.z == z) return dot; }
    var newDot = Dot(x, y, z: z); dots.add(newDot); return newDot;
  }

  Line _getOrAddLine(Dot a, Dot b) {
    for (var line in lines) { if (line.connects(a, b)) return line; }
    var newLine = Line(a, b); lines.add(newLine); return newLine;
  }

  bool playMove(Line lineToPlay, {Player? forcedPlayer}) {
    if (isGameOver) return false;
    Player playerMakingMove = forcedPlayer ?? currentPlayer;
    Line? actualLine;
    for (var l in lines) { if (l.connects(lineToPlay.p1, lineToPlay.p2)) { actualLine = l; break; } }
    if (actualLine == null || actualLine.owner != Player.none || !actualLine.isPlayable) return false;

    actualLine.owner = playerMakingMove;
    lastMove = actualLine;
    bool scoredPoint = false;

    for (var box in boxes) {
      if (box.owner == Player.none && box.isClosed()) {
        box.owner = playerMakingMove; scoredPoint = true;
        int points = box.getCalculatedValue(playerMakingMove);
        if (playerMakingMove == Player.red) scoreRed += points; else scoreBlue += points;
      }
    }

    if (shape == ArenaShape.pyramid3D) _updatePyramidPlayability(); // Ricalcola chi può giocare sopra!

    if (lines.where((l) => l.isPlayable).every((l) => l.owner != Player.none)) isGameOver = true;
    if (!scoredPoint && !isGameOver) currentPlayer = (playerMakingMove == Player.red) ? Player.blue : Player.red;

    return true;
  }
}