import 'dart:math' as math;

import 'package:arcane_helper_utils/arcane_helper_utils.dart';
import 'package:wolf_3d_dart/wolf_3d_data_types.dart';
import 'package:wolf_3d_dart/wolf_3d_engine.dart';

class AsciiTheme {
  /// The character ramp, ordered from most dense (index 0) to least dense (last index).
  final String ramp;

  const AsciiTheme(this.ramp);

  /// Always returns the densest character (e.g., for walls, UI, floors)
  String get solid => ramp[0];

  /// Always returns the completely empty character (e.g., for pitch black darkness)
  String get empty => ramp[ramp.length - 1];

  /// Returns a character based on a 0.0 to 1.0 brightness scale.
  /// 1.0 returns the [solid] character, 0.0 returns the [empty] character.
  String getByBrightness(double brightness) {
    double b = brightness.clamp(0.0, 1.0);
    int index = ((1.0 - b) * (ramp.length - 1)).round();
    return ramp[index];
  }
}

/// A collection of pre-defined character sets
abstract class AsciiThemes {
  static const AsciiTheme blocks = AsciiTheme("█▓▒░ ");
  static const AsciiTheme classic = AsciiTheme("@%#*+=-:. ");
  static const AsciiTheme dense = AsciiTheme("█▇▆▅▄▃▂  ");
}

class ColoredChar {
  final String char;
  final int rawColor; // Stores the AABBGGRR integer from the palette

  ColoredChar(this.char, this.rawColor);

  // Safely extract the exact RGB channels regardless of framework
  int get r => rawColor & 0xFF;
  int get g => (rawColor >> 8) & 0xFF;
  int get b => (rawColor >> 16) & 0xFF;

  // Outputs standard AARRGGBB for Flutter's Color(int) constructor
  int get argb => (0xFF000000) | (r << 16) | (g << 8) | b;
}

class AsciiRasterizer extends Rasterizer {
  AsciiRasterizer({
    this.activeTheme = AsciiThemes.blocks,
    this.aspectMultiplier = 1.0,
    this.verticalStretch = 1.0,
  });

  AsciiTheme activeTheme = AsciiThemes.blocks;

  late List<List<ColoredChar>> _screen;
  late WolfEngine _engine;

  @override
  final double aspectMultiplier;
  @override
  final double verticalStretch;

  // Intercept the base render call to initialize our text grid
  @override
  dynamic render(WolfEngine engine, FrameBuffer buffer) {
    _engine = engine;
    _screen = List.generate(
      buffer.height,
      (_) =>
          List.filled(buffer.width, ColoredChar(' ', ColorPalette.vga32Bit[0])),
    );
    return super.render(engine, buffer);
  }

  @override
  void prepareFrame(WolfEngine engine) {
    // Just grab the raw ints!
    final int ceilingColor = ColorPalette.vga32Bit[25];
    final int floorColor = ColorPalette.vga32Bit[29];

    for (int y = 0; y < height; y++) {
      for (int x = 0; x < width; x++) {
        if (y < viewHeight / 2) {
          _screen[y][x] = ColoredChar(activeTheme.solid, ceilingColor);
        } else if (y < viewHeight) {
          _screen[y][x] = ColoredChar(activeTheme.solid, floorColor);
        }
      }
    }
  }

  @override
  void drawWallColumn(
    int x,
    int drawStart,
    int drawEnd,
    int columnHeight,
    Sprite texture,
    int texX,
    double perpWallDist,
    int side,
  ) {
    double brightness = calculateDepthBrightness(perpWallDist);
    String wallChar = activeTheme.getByBrightness(brightness);

    for (int y = drawStart; y < drawEnd; y++) {
      double relativeY =
          (y - (-columnHeight ~/ 2 + viewHeight ~/ 2)) / columnHeight;
      int texY = (relativeY * 64).toInt().clamp(0, 63);

      int colorByte = texture.pixels[texX * 64 + texY];
      int pixelColor = ColorPalette.vga32Bit[colorByte]; // Raw int

      // Faux directional lighting using your new base class method
      if (side == 1) {
        pixelColor = shadeColor(pixelColor);
      }

      _screen[y][x] = ColoredChar(wallChar, pixelColor);
    }
  }

  @override
  void drawSpriteStripe(
    int stripeX,
    int drawStartY,
    int drawEndY,
    int spriteHeight,
    Sprite texture,
    int texX,
    double transformY,
  ) {
    double brightness = calculateDepthBrightness(transformY);

    for (
      int y = math.max(0, drawStartY);
      y < math.min(viewHeight, drawEndY);
      y++
    ) {
      double relativeY = (y - drawStartY) / spriteHeight;
      int texY = (relativeY * 64).toInt().clamp(0, 63);

      int colorByte = texture.pixels[texX * 64 + texY];
      if (colorByte != 255) {
        int rawColor = ColorPalette.vga32Bit[colorByte];

        // Shade the sprite's actual RGB color based on distance
        int r = (rawColor & 0xFF);
        int g = ((rawColor >> 8) & 0xFF);
        int b = ((rawColor >> 16) & 0xFF);

        r = (r * brightness).toInt();
        g = (g * brightness).toInt();
        b = (b * brightness).toInt();

        int shadedColor = (0xFF000000) | (b << 16) | (g << 8) | r;

        // Force sprites to be SOLID so they don't vanish into the terminal background
        _screen[y][stripeX] = ColoredChar(activeTheme.solid, shadedColor);
      }
    }
  }

  @override
  void drawWeapon(WolfEngine engine) {
    int spriteIndex = engine.player.currentWeapon.getCurrentSpriteIndex(
      engine.data.sprites.length,
    );
    Sprite weaponSprite = engine.data.sprites[spriteIndex];

    int weaponWidth = (width * 0.5).toInt();
    int weaponHeight = (viewHeight * 0.8).toInt();

    int startX = (width ~/ 2) - (weaponWidth ~/ 2);
    int startY =
        viewHeight - weaponHeight + (engine.player.weaponAnimOffset ~/ 4);

    for (int dy = 0; dy < weaponHeight; dy++) {
      for (int dx = 0; dx < weaponWidth; dx++) {
        int texX = (dx * 64 ~/ weaponWidth).clamp(0, 63);
        int texY = (dy * 64 ~/ weaponHeight).clamp(0, 63);

        int colorByte = weaponSprite.pixels[texX * 64 + texY];
        if (colorByte != 255) {
          int drawX = startX + dx;
          int drawY = startY + dy;
          if (drawX >= 0 && drawX < width && drawY >= 0 && drawY < viewHeight) {
            _screen[drawY][drawX] = ColoredChar(
              activeTheme.solid,
              ColorPalette.vga32Bit[colorByte],
            );
          }
        }
      }
    }
  }

  // --- PRIVATE HUD DRAWING HELPER ---

  /// Injects a pure text string directly into the rasterizer grid
  void _writeString(int startX, int y, String text, int color) {
    for (int i = 0; i < text.length; i++) {
      int x = startX + i;
      if (x >= 0 && x < width && y >= 0 && y < height) {
        _screen[y][x] = ColoredChar(text[i], color);
      }
    }
  }

  @override
  void drawHud(WolfEngine engine) {
    // If the terminal is at least 160 columns wide and 50 rows tall,
    // there are enough "pixels" to downscale the VGA image clearly.
    if (width >= 160 && height >= 50) {
      _drawFullVgaHud(engine);
    } else {
      _drawSimpleHud(engine);
    }
  }

  void _drawSimpleHud(WolfEngine engine) {
    // 1. Pull Retro Colors
    final int vgaStatusBarBlue = ColorPalette.vga32Bit[153];
    final int vgaPanelDark = ColorPalette.vga32Bit[0];
    final int white = ColorPalette.vga32Bit[15];
    final int yellow = ColorPalette.vga32Bit[11];
    final int red = ColorPalette.vga32Bit[4];

    // 2. Setup Centered Layout
    // The total width of our standard HUD elements is roughly 120 chars
    const int hudContentWidth = 120;
    final int offsetX = ((width - hudContentWidth) ~/ 2).clamp(0, width);

    // 3. Clear HUD Base
    _fillRect(0, viewHeight, width, height - viewHeight, ' ', vgaStatusBarBlue);
    _writeString(0, viewHeight, "═" * width, white);

    // 4. Panel Drawing Helper
    void drawBorderedPanel(int startX, int startY, int w, int h) {
      _fillRect(startX, startY, w, h, ' ', vgaPanelDark);
      // Horizontal lines
      _writeString(startX, startY, "┌${"─" * (w - 2)}┐", white);
      _writeString(startX, startY + h - 1, "└${"─" * (w - 2)}┘", white);
      // Vertical sides
      for (int i = 1; i < h - 1; i++) {
        _writeString(startX, startY + i, "│", white);
        _writeString(startX + w - 1, startY + i, "│", white);
      }
    }

    // 5. Draw the Panels
    // FLOOR
    drawBorderedPanel(offsetX + 4, viewHeight + 2, 12, 5);
    _writeString(offsetX + 7, viewHeight + 3, "FLOOR", white);
    _writeString(
      offsetX + 9,
      viewHeight + 5,
      engine.activeLevel.name.split(' ').last,
      white,
    );

    // SCORE
    drawBorderedPanel(offsetX + 18, viewHeight + 2, 24, 5);
    _writeString(offsetX + 27, viewHeight + 3, "SCORE", white);
    _writeString(
      offsetX + 27,
      viewHeight + 5,
      engine.player.score.toString().padLeft(6, '0'),
      white,
    );

    // LIVES
    drawBorderedPanel(offsetX + 44, viewHeight + 2, 12, 5);
    _writeString(offsetX + 47, viewHeight + 3, "LIVES", white);
    _writeString(offsetX + 49, viewHeight + 5, "3", white);

    // FACE (With Reactive BJ Logic)
    drawBorderedPanel(offsetX + 58, viewHeight + 1, 14, 7);
    String face = " :-)";
    if (engine.player.health <= 0) {
      face = " X-x";
    } else if (engine.player.damageFlash > 0.1) {
      face = " :-O"; // Mouth open in pain!
    } else if (engine.player.health <= 25) {
      face = " :-(";
    } else if (engine.player.health <= 60) {
      face = " :-|";
    }
    _writeString(offsetX + 63, viewHeight + 4, face, yellow);

    // HEALTH
    int healthColor = engine.player.health > 25 ? white : red;
    drawBorderedPanel(offsetX + 74, viewHeight + 2, 16, 5);
    _writeString(offsetX + 78, viewHeight + 3, "HEALTH", white);
    _writeString(
      offsetX + 79,
      viewHeight + 5,
      "${engine.player.health}%",
      healthColor,
    );

    // AMMO
    drawBorderedPanel(offsetX + 92, viewHeight + 2, 12, 5);
    _writeString(offsetX + 95, viewHeight + 3, "AMMO", white);
    _writeString(offsetX + 97, viewHeight + 5, "${engine.player.ammo}", white);

    // WEAPON
    drawBorderedPanel(offsetX + 106, viewHeight + 2, 14, 5);
    String weapon = engine.player.currentWeapon.type.name.spacePascalCase!
        .toUpperCase();
    if (weapon.length > 12) weapon = weapon.substring(0, 12);
    _writeString(offsetX + 107, viewHeight + 4, weapon, white);
  }

  void _drawFullVgaHud(WolfEngine engine) {
    int statusBarIndex = engine.data.vgaImages.indexWhere(
      (img) => img.width == 320 && img.height == 40,
    );
    if (statusBarIndex == -1) return;

    // 1. Draw Background
    _blitVgaImageAscii(engine.data.vgaImages[statusBarIndex], 0, 160);

    // 2. Draw Stats
    _drawNumberAscii(1, 32, 176, engine.data.vgaImages); // Floor
    _drawNumberAscii(
      engine.player.score,
      96,
      176,
      engine.data.vgaImages,
    ); // Score
    _drawNumberAscii(3, 120, 176, engine.data.vgaImages); // Lives
    _drawNumberAscii(
      engine.player.health,
      192,
      176,
      engine.data.vgaImages,
    ); // Health
    _drawNumberAscii(
      engine.player.ammo,
      232,
      176,
      engine.data.vgaImages,
    ); // Ammo

    // 3. Draw BJ's Face & Current Weapon
    _drawFaceAscii(engine);
    _drawWeaponIconAscii(engine);
  }

  void _drawNumberAscii(
    int value,
    int rightAlignX,
    int startY,
    List<VgaImage> vgaImages,
  ) {
    const int zeroIndex = 96;
    String numStr = value.toString();
    int currentX = rightAlignX - (numStr.length * 8);

    for (int i = 0; i < numStr.length; i++) {
      int digit = int.parse(numStr[i]);
      if (zeroIndex + digit < vgaImages.length) {
        _blitVgaImageAscii(vgaImages[zeroIndex + digit], currentX, startY);
      }
      currentX += 8;
    }
  }

  void _drawFaceAscii(WolfEngine engine) {
    int health = engine.player.health;
    int faceIndex;

    if (health <= 0) {
      faceIndex = 127;
    } else {
      int healthTier = ((100 - health) ~/ 16).clamp(0, 6);
      faceIndex = 106 + (healthTier * 3);
    }

    if (faceIndex < engine.data.vgaImages.length) {
      _blitVgaImageAscii(engine.data.vgaImages[faceIndex], 136, 164);
    }
  }

  void _drawWeaponIconAscii(WolfEngine engine) {
    int weaponIndex = 89;
    if (engine.player.hasChainGun) {
      weaponIndex = 91;
    } else if (engine.player.hasMachineGun) {
      weaponIndex = 90;
    }

    if (weaponIndex < engine.data.vgaImages.length) {
      _blitVgaImageAscii(engine.data.vgaImages[weaponIndex], 256, 164);
    }
  }

  /// Helper to fill a rectangular area with a specific char and background color
  void _fillRect(int startX, int startY, int w, int h, String char, int color) {
    for (int dy = 0; dy < h; dy++) {
      for (int dx = 0; dx < w; dx++) {
        int x = startX + dx;
        int y = startY + dy;
        if (x >= 0 && x < width && y >= 0 && y < height) {
          _screen[y][x] = ColoredChar(char, color);
        }
      }
    }
  }

  @override
  dynamic finalizeFrame() {
    if (_engine.player.damageFlash > 0.0) {
      _applyDamageFlash();
    }
    return _screen;
  }

  // --- PRIVATE HUD DRAWING HELPERS ---

  void _blitVgaImageAscii(VgaImage image, int startX_320, int startY_200) {
    int planeWidth = image.width ~/ 4;
    int planeSize = planeWidth * image.height;

    double scaleX = width / 320.0;
    double scaleY = height / 200.0;

    int destStartX = (startX_320 * scaleX).toInt();
    int destStartY = (startY_200 * scaleY).toInt();
    int destWidth = (image.width * scaleX).toInt();
    int destHeight = (image.height * scaleY).toInt();

    for (int dy = 0; dy < destHeight; dy++) {
      for (int dx = 0; dx < destWidth; dx++) {
        int drawX = destStartX + dx;
        int drawY = destStartY + dy;

        if (drawX >= 0 && drawX < width && drawY >= 0 && drawY < height) {
          int srcX = (dx / scaleX).toInt().clamp(0, image.width - 1);
          int srcY = (dy / scaleY).toInt().clamp(0, image.height - 1);

          int plane = srcX % 4;
          int sx = srcX ~/ 4;
          int index = (plane * planeSize) + (srcY * planeWidth) + sx;

          int colorByte = image.pixels[index];
          if (colorByte != 255) {
            _screen[drawY][drawX] = ColoredChar(
              activeTheme.solid,
              ColorPalette.vga32Bit[colorByte],
            );
          }
        }
      }
    }
  }

  // --- DAMAGE FLASH ---
  void _applyDamageFlash() {
    double intensity = _engine.player.damageFlash;
    int redBoost = (150 * intensity).toInt();
    double colorDrop = 1.0 - (0.5 * intensity);

    for (int y = 0; y < viewHeight; y++) {
      for (int x = 0; x < width; x++) {
        ColoredChar cell = _screen[y][x];

        // Use our safe getters!
        int r = cell.r;
        int g = cell.g;
        int b = cell.b;

        r = (r + redBoost).clamp(0, 255);
        g = (g * colorDrop).toInt().clamp(0, 255);
        b = (b * colorDrop).toInt().clamp(0, 255);

        // Pack back into the native AABBGGRR format that ColoredChar expects
        int newRawColor = (0xFF000000) | (b << 16) | (g << 8) | r;

        _screen[y][x] = ColoredChar(cell.char, newRawColor);
      }
    }
  }

  /// Converts the current frame to a single printable ANSI string
  String toAnsiString() {
    StringBuffer buffer = StringBuffer();

    int lastR = -1;
    int lastG = -1;
    int lastB = -1;

    for (int y = 0; y < _screen.length; y++) {
      List<ColoredChar> row = _screen[y];
      for (ColoredChar cell in row) {
        if (cell.r != lastR || cell.g != lastG || cell.b != lastB) {
          buffer.write('\x1b[38;2;${cell.r};${cell.g};${cell.b}m');
          lastR = cell.r;
          lastG = cell.g;
          lastB = cell.b;
        }
        buffer.write(cell.char);
      }

      // Only print a newline if we are NOT on the very last row.
      // This stops the terminal from scrolling down!
      if (y < _screen.length - 1) {
        buffer.write('\n');
      }
    }

    // Reset the terminal color at the very end
    buffer.write('\x1b[0m');

    return buffer.toString();
  }
}
