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

import 'dart:math';

enum Player { red, blue, none }
enum BoxType { normal, gold, bomb, invisible, swap, ice, multiplier } // Aggiunti ice e multiplier
enum ArenaShape { classic, cross, donut, hourglass, chaos }

class Dot {
  final int x;
  final int y;
  Dot(this.x, this.y);

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

class Line {
  final Dot p1;
  final Dot p2;
  Player owner = Player.none;
  bool isPlayable = false;
  bool isIceCracked = false; // NUOVO: Stato per il blocco di ghiaccio

  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;
  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);

  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; // Il moltiplicatore e il ghiaccio non danno punti base
    return 1;
  }
}

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;

  // Variabili per il Moltiplicatore
  bool redHasMultiplier = false;
  bool blueHasMultiplier = false;

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

  void _generateBoard() {
    final random = seed != null ? Random(seed) : Random();
    int chaosAlgorithm = random.nextInt(5);

    if (shape == ArenaShape.chaos) {
      columns = radius * 2 + 1;
      rows = (radius * 3) + 2;
    } else {
      columns = radius * 2 + 1;
      rows = radius * 2 + 1;
    }

    dots.clear();
    lines.clear();
    boxes.clear();
    lastMove = null;

    for (int y = 0; y < rows; y++) {
      for (int x = 0; x < columns; x++) {
        var box = Box(x, y);
        bool isVisible = true;

        if (shape != ArenaShape.chaos) {
          int dx = (x - radius).abs();
          int dy = (y - radius).abs();
          isVisible = (dx + dy) <= radius;

          if (isVisible) {
            switch (shape) {
              case ArenaShape.cross:
                int spessoreBraccio = radius > 3 ? 1 : 0;
                if (dx > spessoreBraccio && dy > spessoreBraccio) isVisible = false; break;
              case ArenaShape.donut:
                int dimensioneBuco = radius > 3 ? 2 : 1;
                if ((dx + dy) <= dimensioneBuco) isVisible = false; break;
              case ArenaShape.hourglass:
                if (dx > dy) isVisible = false;
                if (x == radius && y == radius) isVisible = true; break;
              default: break;
            }
          }
        } else {
          double percentY = y / rows;
          if (chaosAlgorithm == 0) {
            isVisible = (x % 2 == 0) && (random.nextDouble() > 0.15);
          } else if (chaosAlgorithm == 1) {
            double chance = 0.2 + (percentY * 0.7);
            isVisible = random.nextDouble() < chance;
          } else if (chaosAlgorithm == 2) {
            int midY = rows ~/ 2;
            int distFromCenterY = (y - midY).abs();
            int allowedWidth = (distFromCenterY / midY * radius).ceil() + 1;
            int dx = (x - radius).abs();
            isVisible = dx <= allowedWidth && random.nextDouble() > 0.1;
          } else if (chaosAlgorithm == 3) {
            isVisible = (y % 2 == 0) ? (x < columns - 1) : (x > 0);
            if (random.nextDouble() > 0.8) isVisible = false;
          } else if (chaosAlgorithm == 4) {
            isVisible = random.nextDouble() > 0.45;
          }
          if (x == radius && y == rows ~/ 2) isVisible = true;
        }

        if (!isVisible) {
          box.type = BoxType.invisible;
        } else if (level > 1) {
          double chance = random.nextDouble();
          if (chance < 0.08) box.type = BoxType.gold;
          else if (chance > 0.92) box.type = BoxType.bomb;
          else if (level >= 5 && chance > 0.88 && chance <= 0.92) box.type = BoxType.swap;
          else if (level >= 10 && chance > 0.83 && chance <= 0.88) box.type = BoxType.ice; // Nuova Scatola Ghiaccio
          else if (level >= 15 && chance > 0.78 && chance <= 0.83) box.type = BoxType.multiplier; // Nuova Scatola x2
        }
        boxes.add(box);
      }
    }

    for (var box in boxes) {
      Dot tl = _getOrAddDot(box.x, box.y);
      Dot tr = _getOrAddDot(box.x + 1, box.y);
      Dot bl = _getOrAddDot(box.x, box.y + 1);
      Dot br = _getOrAddDot(box.x + 1, box.y + 1);

      box.top = _getOrAddLine(tl, tr);
      box.bottom = _getOrAddLine(bl, br);
      box.left = _getOrAddLine(tl, bl);
      box.right = _getOrAddLine(tr, br);

      if (box.type != BoxType.invisible) {
        box.top.isPlayable = true; box.bottom.isPlayable = true;
        box.left.isPlayable = true; box.right.isPlayable = true;
      }
    }
  }

  Dot _getOrAddDot(int x, int y) {
    for (var dot in dots) { if (dot.x == x && dot.y == y) return dot; }
    var newDot = Dot(x, y);
    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;

    // --- LOGICA BLOCCO DI GHIACCIO ---
    bool closesIce = false;
    for (var box in boxes) {
      if (box.type == BoxType.ice && box.owner == Player.none) {
        int linesCount = 0;
        if (box.top.owner != Player.none || box.top == actualLine) linesCount++;
        if (box.bottom.owner != Player.none || box.bottom == actualLine) linesCount++;
        if (box.left.owner != Player.none || box.left == actualLine) linesCount++;
        if (box.right.owner != Player.none || box.right == actualLine) linesCount++;
        if (linesCount == 4) closesIce = true;
      }
    }

    if (closesIce && !actualLine.isIceCracked) {
      actualLine.isIceCracked = true; // Si incrina ma non si chiude!
      lastMove = actualLine;
      if (forcedPlayer == null) currentPlayer = (currentPlayer == Player.red) ? Player.blue : Player.red;
      else currentPlayer = (forcedPlayer == Player.red) ? Player.blue : Player.red;
      return true; // Mossa valida, ma turno finito.
    }

    // Mossa normale o secondo colpo al ghiaccio
    actualLine.isIceCracked = false;
    actualLine.owner = playerMakingMove;
    lastMove = actualLine;

    bool scoredPoint = false;
    bool triggeredSwap = false;

    for (var box in boxes) {
      if (box.owner == Player.none && box.isClosed()) {
        box.owner = playerMakingMove;
        scoredPoint = true;

        if (box.hiddenJokerOwner != null) box.isJokerRevealed = true;

        int points = box.getCalculatedValue(playerMakingMove);

        // --- LOGICA MOLTIPLICATORE x2 ---
        if (box.type == BoxType.multiplier) {
          if (playerMakingMove == Player.red) redHasMultiplier = true;
          else blueHasMultiplier = true;
        } else if (points != 0) {
          // Se la scatola chiusa dà punti e il giocatore ha un x2 attivo...
          if (playerMakingMove == Player.red && redHasMultiplier) {
            points *= 2;
            redHasMultiplier = false; // Si consuma
          } else if (playerMakingMove == Player.blue && blueHasMultiplier) {
            points *= 2;
            blueHasMultiplier = false; // Si consuma
          }
        }

        if (playerMakingMove == Player.red) { scoreRed += points; }
        else { scoreBlue += points; }

        if (box.type == BoxType.swap && box.hiddenJokerOwner == null) {
          triggeredSwap = true;
        }
      }

      if (box.type == BoxType.invisible && !box.isRevealed) {
        if (box.top.owner != Player.none && box.bottom.owner != Player.none &&
            box.left.owner != Player.none && box.right.owner != Player.none) {
          box.isRevealed = true;
        }
      }
    }

    if (triggeredSwap) {
      int temp = scoreRed; scoreRed = scoreBlue; scoreBlue = temp;
    }

    if (lines.where((l) => l.isPlayable).every((l) => l.owner != Player.none)) { isGameOver = true; }

    if (forcedPlayer == null) {
      if (!scoredPoint && !isGameOver) { currentPlayer = (currentPlayer == Player.red) ? Player.blue : Player.red; }
      else if (scoredPoint && !isGameOver) { currentPlayer = playerMakingMove; }
    } else {
      if (!scoredPoint && !isGameOver) { currentPlayer = (forcedPlayer == Player.red) ? Player.blue : Player.red; }
      else { currentPlayer = forcedPlayer; }
    }

    return true;
  }
}