import 'package:wolf_3d_dart/src/raycasting/projection.dart';
import 'package:wolf_3d_dart/src/raycasting/raycaster.dart';
import 'package:wolf_3d_dart/wolf_3d_data_types.dart';
import 'package:wolf_3d_dart/wolf_3d_engine.dart';

/// Shared rendering pipeline for Wolf3D backends.
///
/// Subclasses implement draw primitives for their output target (software
/// framebuffer, ANSI text, Sixel, etc), while this backend coordinates frame
/// orchestration and delegates DDA/sprite math to [Raycaster].
abstract class RendererBackend<T>
    with ProjectionMath
    implements RaycastBackend {
  @override
  List<double> zBuffer = <double>[];
  late int width;
  late int height;
  @override
  late int viewHeight;

  /// The current engine instance; set at the start of every [render] call.
  late WolfEngine engine;

  final Raycaster _raycaster = Raycaster();

  /// A multiplier to adjust the width of sprites.
  /// Pixel renderers usually keep this at 1.0.
  /// ASCII renderers can override this (e.g., 0.6) to account for tall characters.
  @override
  double get aspectMultiplier => 1.0;

  /// A multiplier to counteract tall pixel formats (like 1:2 terminal fonts).
  /// Defaults to 1.0 (no squish) for standard pixel rendering.
  @override
  double get verticalStretch => 1.0;

  /// The logical width of the projection area used for raycasting and sprites.
  /// Most renderers use the full buffer width.
  @override
  int get projectionWidth => width;

  /// Horizontal offset of the projection area within the output buffer.
  @override
  int get projectionOffsetX => 0;

  /// The logical height of the 3D projection before a renderer maps rows to output pixels.
  /// Most renderers use the visible view height. Terminal ASCII can override this to render
  /// more vertical detail and collapse it into half-block glyphs.
  @override
  int get projectionViewHeight => viewHeight;

  /// Whether the current terminal dimensions are supported by this renderer.
  /// Default renderers accept all sizes.
  bool isTerminalSizeSupported(int columns, int rows) => true;

  /// Human-readable requirement text used by the host app when size checks fail.
  String get terminalSizeRequirement => 'Please resize your terminal window.';

  void _ensureZBuffer() {
    if (zBuffer.length != projectionWidth) {
      zBuffer = List<double>.filled(projectionWidth, 0.0);
      return;
    }
    zBuffer.fillRange(0, zBuffer.length, 0.0);
  }

  /// The main entry point called by the game loop.
  /// Orchestrates the rendering pipeline.
  T render(WolfEngine engine) {
    this.engine = engine;
    width = engine.frameBuffer.width;
    height = engine.frameBuffer.height;
    // The 3D view typically takes up the top 80% of the screen.
    viewHeight = (height * 0.8).toInt();
    _ensureZBuffer();

    // 1. Setup the frame (clear screen, draw floor/ceiling).
    prepareFrame(engine);

    if (engine.isMenuOpen) {
      drawMenu(engine);
      if (engine.showFpsCounter) {
        drawFpsOverlay(engine);
      }
      return finalizeFrame();
    }

    // 2. Do the heavy math for wall and sprite casting.
    _raycaster.castWorld(engine, this);

    // 3. Draw 2D overlays.
    drawWeapon(engine);
    drawHud(engine);
    if (engine.showFpsCounter) {
      drawFpsOverlay(engine);
    }

    // 4. Finalize and return the frame data (Buffer or String/List).
    return finalizeFrame();
  }

  // ===========================================================================
  // ABSTRACT METHODS (Implemented by backend subclasses)
  // ===========================================================================

  /// Initialize buffers, clear the screen, and draw the floor/ceiling.
  void prepareFrame(WolfEngine engine);

  /// Draw a single vertical column of a wall.
  @override
  void drawWallColumn(
    int x,
    int drawStart,
    int drawEnd,
    int columnHeight,
    Sprite texture,
    int texX,
    double perpWallDist,
    int side,
  );

  /// Draw a single vertical stripe of a sprite (enemy/item).
  @override
  void drawSpriteStripe(
    int stripeX,
    int drawStartY,
    int drawEndY,
    int spriteHeight,
    Sprite texture,
    int texX,
    double transformY,
  );

  /// Draw the player's weapon overlay at the bottom of the 3D view.
  void drawWeapon(WolfEngine engine);

  /// Draw the 2D status bar at the bottom 20% of the screen.
  void drawHud(WolfEngine engine);

  /// Return the finished frame (e.g., the FrameBuffer itself, or an ASCII list).
  T finalizeFrame();

  /// Draws a non-world menu frame when the engine is awaiting configuration.
  ///
  /// Default implementation is a no-op for backends that don't support menus.
  void drawMenu(WolfEngine engine) {}

  /// Draws a small FPS diagnostic in the top-left corner.
  ///
  /// Backends can override this to render text in their native format.
  void drawFpsOverlay(WolfEngine engine) {}

  /// Returns a compact FPS label used by renderer-specific overlays.
  String fpsLabel(WolfEngine engine) => 'FPS ${engine.fps.round()}';

  /// Plots a VGA image into this backend's HUD coordinate space.
  ///
  /// Coordinates are in the original 320x200 HUD space. Backends that support
  /// shared HUD composition should override this.
  void blitHudVgaImage(VgaImage image, int startX320, int startY200) {}

  /// Shared Wolf3D VGA HUD sequence used by software/sixel/ASCII-full-HUD.
  ///
  /// Coordinates are intentionally in original 320x200 HUD space so each
  /// backend can scale/map them consistently via [blitHudVgaImage].
  void drawStandardVgaHud(WolfEngine engine) {
    final List<VgaImage> vgaImages = engine.data.vgaImages;
    final statusBarRef = engine.data.registry.hud.resolve(HudKey.statusBar);
    final int statusBarIndex = statusBarRef?.vgaIndex ?? -1;
    if (statusBarIndex >= 0 && statusBarIndex < vgaImages.length) {
      blitHudVgaImage(vgaImages[statusBarIndex], 0, 160);
    }

    _drawHudNumber(engine, vgaImages, 1, 32, 176);
    _drawHudNumber(engine, vgaImages, engine.player.score, 96, 176);
    _drawHudNumber(engine, vgaImages, 3, 120, 176);
    _drawHudNumber(engine, vgaImages, engine.player.health, 192, 176);
    _drawHudNumber(engine, vgaImages, engine.player.ammo, 232, 176);
    _drawHudFace(engine, vgaImages);
    _drawHudWeaponIcon(engine, vgaImages);
  }

  void _drawHudNumber(
    WolfEngine engine,
    List<VgaImage> vgaImages,
    int value,
    int rightAlignX,
    int startY,
  ) {
    const digitKeys = [
      HudKey.digit0,
      HudKey.digit1,
      HudKey.digit2,
      HudKey.digit3,
      HudKey.digit4,
      HudKey.digit5,
      HudKey.digit6,
      HudKey.digit7,
      HudKey.digit8,
      HudKey.digit9,
    ];

    final String numStr = value.toString();
    int currentX = rightAlignX - (numStr.length * 8);

    for (int i = 0; i < numStr.length; i++) {
      final int digit = int.parse(numStr[i]);
      final ref = engine.data.registry.hud.resolve(digitKeys[digit]);
      final imageIndex = ref?.vgaIndex ?? -1;
      if (imageIndex >= 0 && imageIndex < vgaImages.length) {
        blitHudVgaImage(vgaImages[imageIndex], currentX, startY);
      }
      currentX += 8;
    }
  }

  void _drawHudFace(WolfEngine engine, List<VgaImage> vgaImages) {
    final faceRef = engine.data.registry.hud.faceForHealth(
      engine.player.health,
    );
    final int faceIndex = faceRef?.vgaIndex ?? -1;
    if (faceIndex >= 0 && faceIndex < vgaImages.length) {
      blitHudVgaImage(vgaImages[faceIndex], 136, 164);
    }
  }

  void _drawHudWeaponIcon(WolfEngine engine, List<VgaImage> vgaImages) {
    final HudKey weaponKey = engine.player.hasChainGun
        ? HudKey.chainGunIcon
        : engine.player.hasMachineGun
        ? HudKey.machineGunIcon
        : HudKey.pistolIcon;
    final weaponRef = engine.data.registry.hud.resolve(weaponKey);
    final int weaponIndex = weaponRef?.vgaIndex ?? -1;
    if (weaponIndex >= 0 && weaponIndex < vgaImages.length) {
      blitHudVgaImage(vgaImages[weaponIndex], 256, 164);
    }
  }

  /// Calculates depth-based lighting falloff (0.0 to 1.0).
  /// While the original Wolf3D didn't use depth fog, this provides a great
  /// atmospheric effect for custom backends (like ASCII dithering).
  double calculateDepthBrightness(double distance) {
    return (10.0 / (distance + 2.0)).clamp(0.0, 1.0);
  }

  /// Returns the VGA image index for BJ's face sprite based on player health.
  int hudFaceVgaIndex(int health) {
    if (health <= 0) return 127;
    return 106 + (((100 - health) ~/ 16).clamp(0, 6) * 3);
  }

  /// Returns the VGA image index for the current weapon icon in the HUD.
  int hudWeaponVgaIndex(WolfEngine engine) {
    if (engine.player.hasChainGun) return 91;
    if (engine.player.hasMachineGun) return 90;
    return 89;
  }

  /// Darkens a 32-bit 0xAABBGGRR color by roughly 30% without touching alpha.
  int shadeColor(int color) {
    final int r = (color & 0xFF) * 7 ~/ 10;
    final int g = ((color >> 8) & 0xFF) * 7 ~/ 10;
    final int b = ((color >> 16) & 0xFF) * 7 ~/ 10;
    return (0xFF000000) | (b << 16) | (g << 8) | r;
  }
}
