// ===========================================================================
// FILE: lib/ui/game/board_painter.dart
// ===========================================================================

import 'dart:math';
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';

import '../../models/game_board.dart';
import '../../core/app_colors.dart';

class BoardPainter extends CustomPainter {
  final GameBoard board;
  final ThemeColors theme;
  final AppThemeType themeType;
  final double blinkValue;

  final bool isOnline;
  final bool isVsCPU;
  final bool isSetupPhase;
  final Player myPlayer;
  final Player jokerTurn;

  final double cameraAngle; // Angolazione della telecamera a 360 gradi!

  BoardPainter({
    required this.board,
    required this.theme,
    required this.themeType,
    required this.isOnline,
    required this.isVsCPU,
    required this.isSetupPhase,
    required this.myPlayer,
    required this.jokerTurn,
    this.blinkValue = 0.0,
    this.cameraAngle = 0.0,
  });

  Color _darken(Color c, [double amount = .1]) {
    assert(amount >= 0 && amount <= 1);
    final hsl = HSLColor.fromColor(c);
    final hslDark = hsl.withLightness((hsl.lightness - amount).clamp(0.0, 1.0));
    return hslDark.toColor();
  }

  // LA MAGIA: Proiezione Isometrica Ruotabile
  Offset projectLogical(double x, double y, double z, Size size) {
    if (board.shape != ArenaShape.pyramid3D) {
      int gridPoints = board.columns + 1;
      double spacing = size.width / gridPoints;
      return Offset(x * spacing + (spacing / 2), y * spacing + (spacing / 2));
    }

    double tileW = size.width / 3.8;
    double tileH = tileW * 0.55;
    double zHeight = tileW * 0.45; // L'altezza fisica del blocco 3D

    // Calcoliamo la posizione tenendo conto del restringimento della piramide (+ z * 0.5)
    double actualX = x + z * 0.5 - board.columns / 2.0;
    double actualY = y + z * 0.5 - board.rows / 2.0;

    // Matrice di rotazione della telecamera (Z-Axis)
    double rx = actualX * cos(cameraAngle) - actualY * sin(cameraAngle);
    double ry = actualX * sin(cameraAngle) + actualY * cos(cameraAngle);

    // Proiezione Isometrica 2D
    double sx = (rx - ry) * tileW;
    // Il SEGRETO: -z sposta fisicamente in ALTO la faccia del cubo rispetto allo schermo
    double sy = (rx + ry) * tileH - (z * zHeight);

    return Offset(size.width / 2 + sx, size.height * 0.70 + sy);
  }

  // Calcola la distanza dalla telecamera per lo Z-Buffering
  double getDepth(Box b) {
    double actualX = b.x + b.z * 0.5 - board.columns / 2.0;
    double actualY = b.y + b.z * 0.5 - board.rows / 2.0;
    double rx = actualX * cos(cameraAngle) - actualY * sin(cameraAngle);
    double ry = actualX * sin(cameraAngle) + actualY * cos(cameraAngle);
    return rx + ry; // Ordina dal più lontano al più vicino
  }

  @override
  void paint(Canvas canvas, Size size) {
    if (board.shape == ArenaShape.pyramid3D) _paint3D(canvas, size);
    else _paint2D(canvas, size);
  }

  void _paint3D(Canvas canvas, Size size) {
    double visualZHeight = (size.width / 3.8) * 0.45;
    int maxZ = 4;
    Set<Line> drawnLines = {};

    // Costruiamo la piramide piano per piano, partendo dal basso
    for (int currentZ = 0; currentZ < maxZ; currentZ++) {
      var currentLevelBoxes = board.boxes.where((b) => b.z == currentZ && b.type != BoxType.invisible).toList();

      // Ordiniamo dal fondo allo schermo
      currentLevelBoxes.sort((a, b) => getDepth(a).compareTo(getDepth(b)));

      for (var box in currentLevelBoxes) {
        bool isPlayable = box.top.isPlayable;
        bool isOwned = box.owner != Player.none;
        if (!isOwned && !isPlayable) continue;

        // Disegniamo prima le LINEE DELLA GRIGLIA relative a questo cubo
        void drawLine(Line l, double lx1, double ly1, double lx2, double ly2) {
          if (drawnLines.contains(l)) return;
          drawnLines.add(l);
          if (!l.isPlayable && l.owner == Player.none) return;

          Offset pt1 = projectLogical(lx1, ly1, currentZ.toDouble(), size);
          Offset pt2 = projectLogical(lx2, ly2, currentZ.toDouble(), size);

          if (l.isIceCracked) { _drawCrackedIceLine(canvas, pt1, pt2, blinkValue); return; }
          Color lineColor = l.owner == Player.none ? theme.gridLine.withOpacity(0.5) : (l.owner == Player.red ? theme.playerRed : theme.playerBlue);
          if (l == board.lastMove && l.owner != Player.none) canvas.drawLine(pt1, pt2, Paint()..color = Colors.white.withOpacity(blinkValue * 0.8)..strokeWidth = 10.0..strokeCap = StrokeCap.round..maskFilter = const MaskFilter.blur(BlurStyle.normal, 4.0));
          canvas.drawLine(pt1, pt2, Paint()..color = lineColor..strokeWidth = 4.0..strokeCap = StrokeCap.round);
        }

        drawLine(box.top, box.x.toDouble(), box.y.toDouble(), box.x + 1.0, box.y.toDouble());
        drawLine(box.right, box.x + 1.0, box.y.toDouble(), box.x + 1.0, box.y + 1.0);
        drawLine(box.bottom, box.x.toDouble(), box.y + 1.0, box.x + 1.0, box.y + 1.0);
        drawLine(box.left, box.x.toDouble(), box.y.toDouble(), box.x.toDouble(), box.y + 1.0);

        // Disegniamo i PALLINI
        void drawDot(Dot d) {
          if (board.lines.any((l) => (l.p1 == d || l.p2 == d) && l.isPlayable)) {
            canvas.drawCircle(projectLogical(d.x.toDouble(), d.y.toDouble(), currentZ.toDouble(), size), 5.0, Paint()..color = theme.text.withOpacity(0.8));
          }
        }
        drawDot(box.top.p1); drawDot(box.top.p2); drawDot(box.bottom.p2); drawDot(box.bottom.p1);

        // --- IL CUBO SOLIDO ---
        if (isOwned) {
          // Calcoliamo i 4 vertici del TETTO (Z + 1)
          List<Offset> topCorners = [
            projectLogical(box.x.toDouble(), box.y.toDouble(), currentZ + 1.0, size),
            projectLogical(box.x + 1.0, box.y.toDouble(), currentZ + 1.0, size),
            projectLogical(box.x + 1.0, box.y + 1.0, currentZ + 1.0, size),
            projectLogical(box.x.toDouble(), box.y + 1.0, currentZ + 1.0, size),
          ];

          // Algoritmo Geometrico Infallibile: troviamo la silhouette
          topCorners.sort((a, b) => a.dy.compareTo(b.dy));
          Offset screenTop = topCorners[0];     // Il punto più alto sullo schermo
          Offset screenBottom = topCorners[3];  // Il punto più basso sullo schermo
          Offset screenLeft = topCorners[1].dx < topCorners[2].dx ? topCorners[1] : topCorners[2];
          Offset screenRight = topCorners[1].dx > topCorners[2].dx ? topCorners[1] : topCorners[2];

          Color baseColor = box.owner == Player.red ? theme.playerRed : theme.playerBlue;

          // PARETE SINISTRA: Dal tetto scende verso il basso
          Path leftWall = Path()
            ..moveTo(screenLeft.dx, screenLeft.dy)
            ..lineTo(screenBottom.dx, screenBottom.dy)
            ..lineTo(screenBottom.dx, screenBottom.dy + visualZHeight)
            ..lineTo(screenLeft.dx, screenLeft.dy + visualZHeight)
            ..close();
          canvas.drawPath(leftWall, Paint()..color = _darken(baseColor, 0.15)..style = PaintingStyle.fill);
          canvas.drawPath(leftWall, Paint()..color = Colors.black.withOpacity(0.3)..style = PaintingStyle.stroke..strokeWidth = 1.0);

          // PARETE DESTRA: Dal tetto scende verso il basso
          Path rightWall = Path()
            ..moveTo(screenBottom.dx, screenBottom.dy)
            ..lineTo(screenRight.dx, screenRight.dy)
            ..lineTo(screenRight.dx, screenRight.dy + visualZHeight)
            ..lineTo(screenBottom.dx, screenBottom.dy + visualZHeight)
            ..close();
          canvas.drawPath(rightWall, Paint()..color = _darken(baseColor, 0.35)..style = PaintingStyle.fill);
          canvas.drawPath(rightWall, Paint()..color = Colors.black.withOpacity(0.3)..style = PaintingStyle.stroke..strokeWidth = 1.0);

          // IL TETTO: Disegnato per ultimo, copre i muri ed è solido
          Path roof = Path()..moveTo(screenTop.dx, screenTop.dy)..lineTo(screenRight.dx, screenRight.dy)..lineTo(screenBottom.dx, screenBottom.dy)..lineTo(screenLeft.dx, screenLeft.dy)..close();
          canvas.drawPath(roof, Paint()..color = baseColor..style = PaintingStyle.fill);
          canvas.drawPath(roof, Paint()..color = Colors.white.withOpacity(0.5)..style = PaintingStyle.stroke..strokeWidth = 2.0);

          _drawBoxIcon(canvas, roof, box);

        } else if (isPlayable) {
          // PAVIMENTO VUOTO
          Offset f0 = projectLogical(box.x.toDouble(), box.y.toDouble(), currentZ.toDouble(), size);
          Offset f1 = projectLogical(box.x + 1.0, box.y.toDouble(), currentZ.toDouble(), size);
          Offset f2 = projectLogical(box.x + 1.0, box.y + 1.0, currentZ.toDouble(), size);
          Offset f3 = projectLogical(box.x.toDouble(), box.y + 1.0, currentZ.toDouble(), size);
          Path floor = Path()..moveTo(f0.dx, f0.dy)..lineTo(f1.dx, f1.dy)..lineTo(f2.dx, f2.dy)..lineTo(f3.dx, f3.dy)..close();

          canvas.drawPath(floor, Paint()..color = Colors.white.withOpacity(0.08)..style = PaintingStyle.fill);
          _drawBoxIcon(canvas, floor, box);
        }
      }
    }
  }

  void _paint2D(Canvas canvas, Size size) {
    for (var box in board.boxes) {
      if (box.type == BoxType.invisible) continue;
      Offset p1 = projectLogical(box.top.p1.x.toDouble(), box.top.p1.y.toDouble(), 0, size);
      Offset p2 = projectLogical(box.top.p2.x.toDouble(), box.top.p2.y.toDouble(), 0, size);
      Offset p3 = projectLogical(box.bottom.p2.x.toDouble(), box.bottom.p2.y.toDouble(), 0, size);
      Offset p4 = projectLogical(box.bottom.p1.x.toDouble(), box.bottom.p1.y.toDouble(), 0, size);
      Path poly = Path()..moveTo(p1.dx, p1.dy)..lineTo(p2.dx, p2.dy)..lineTo(p3.dx, p3.dy)..lineTo(p4.dx, p4.dy)..close();

      if (box.owner != Player.none) {
        Color c = box.owner == Player.red ? theme.playerRed : theme.playerBlue;
        canvas.drawPath(poly, Paint()..color = c.withOpacity(0.85)..style = PaintingStyle.fill);
      }
      _drawBoxIcon(canvas, poly, box);
    }

    for (var line in board.lines) {
      if (!line.isPlayable && line.owner == Player.none) continue;
      Offset p1 = projectLogical(line.p1.x.toDouble(), line.p1.y.toDouble(), 0, size);
      Offset p2 = projectLogical(line.p2.x.toDouble(), line.p2.y.toDouble(), 0, size);

      if (line.isIceCracked) { _drawCrackedIceLine(canvas, p1, p2, blinkValue); continue; }
      Color lineColor = line.owner == Player.none ? theme.gridLine.withOpacity(0.4) : (line.owner == Player.red ? theme.playerRed : theme.playerBlue);
      if (line == board.lastMove && line.owner != Player.none) canvas.drawLine(p1, p2, Paint()..color = Colors.white.withOpacity(blinkValue * 0.8)..strokeWidth = 12.0..strokeCap = StrokeCap.round..maskFilter = const MaskFilter.blur(BlurStyle.normal, 4.0));
      canvas.drawLine(p1, p2, Paint()..color = lineColor..strokeWidth = (line.owner == Player.none ? 3.0 : 6.0)..strokeCap = StrokeCap.round);
    }

    for (var dot in board.dots) {
      bool isVisible = board.lines.any((l) => (l.p1 == dot || l.p2 == dot) && l.isPlayable);
      if (isVisible) canvas.drawCircle(projectLogical(dot.x.toDouble(), dot.y.toDouble(), 0, size), 4.0, Paint()..color = theme.text.withOpacity(0.8));
    }
  }

  void _drawBoxIcon(Canvas canvas, Path face, Box box) {
    if (box.type == BoxType.gold) _drawIconOnPath(canvas, face, FontAwesomeIcons.crown, Colors.amber);
    else if (box.type == BoxType.bomb) _drawIconOnPath(canvas, face, FontAwesomeIcons.skull, Colors.redAccent);
    else if (box.type == BoxType.swap) _drawIconOnPath(canvas, face, FontAwesomeIcons.arrowsRotate, Colors.purpleAccent);
    else if (box.type == BoxType.multiplier) _drawIconOnPath(canvas, face, FontAwesomeIcons.bolt, Colors.yellowAccent);
    else if (box.type == BoxType.ice && box.owner == Player.none) {
      canvas.drawPath(face, Paint()..color = Colors.cyanAccent.withOpacity(0.15)..style = PaintingStyle.fill);
      _drawIconOnPath(canvas, face, FontAwesomeIcons.snowflake, Colors.cyanAccent);
    }
  }

  void _drawIconOnPath(Canvas canvas, Path path, IconData icon, Color color) {
    Rect bounds = path.getBounds();
    TextPainter tp = TextPainter(text: TextSpan(text: String.fromCharCode(icon.codePoint), style: TextStyle(color: color, fontSize: 16, fontFamily: icon.fontFamily, package: icon.fontPackage)), textDirection: TextDirection.ltr)..layout();
    tp.paint(canvas, Offset(bounds.center.dx - tp.width / 2, bounds.center.dy - tp.height / 2));
  }

  void _drawCrackedIceLine(Canvas canvas, Offset p1, Offset p2, double blink) {
    Paint crackPaint = Paint()..color = Colors.cyanAccent.withOpacity(0.6 + (0.4 * blink))..strokeWidth = 3.0..style = PaintingStyle.stroke..strokeCap = StrokeCap.round..maskFilter = const MaskFilter.blur(BlurStyle.solid, 2.0);
    canvas.drawLine(p1, p2, Paint()..color = Colors.cyan.withOpacity(0.2)..strokeWidth=6.0);
    double dx = p2.dx - p1.dx; double dy = p2.dy - p1.dy;
    double len = sqrt(dx * dx + dy * dy);
    if (len == 0) return;
    double ndx = dx / len; double ndy = dy / len;
    Path crack = Path()..moveTo(p1.dx, p1.dy);
    for (int i = 1; i < 6; i++) {
      double offset = (i % 2 == 0 ? 3.0 : -3.0);
      crack.lineTo(p1.dx + ndx * (len * (i / 6)) + (-ndy) * offset, p1.dy + ndy * (len * (i / 6)) + ndx * offset);
    }
    crack.lineTo(p2.dx, p2.dy);
    canvas.drawPath(crack, crackPaint);
  }

  @override bool shouldRepaint(covariant BoardPainter oldDelegate) => true;
}