import 'dart:math' as math;

import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:wolf_3d_data_types/wolf_3d_data_types.dart';
import 'package:wolf_3d_engine/wolf_3d_engine.dart';
import 'package:wolf_3d_entities/wolf_3d_entities.dart';
import 'package:wolf_dart/features/renderer/raycast_painter.dart';
import 'package:wolf_dart/features/renderer/weapon_painter.dart';
import 'package:wolf_dart/features/ui/hud.dart';
import 'package:wolf_dart/wolf_3d.dart';

class WolfRenderer extends StatefulWidget {
  const WolfRenderer(
    this.data, {
    required this.difficulty,
    required this.startingEpisode,
    super.key,
  });

  final WolfensteinData data;
  final Difficulty difficulty;
  final int startingEpisode;

  @override
  State<WolfRenderer> createState() => _WolfRendererState();
}

class _WolfRendererState extends State<WolfRenderer>
    with SingleTickerProviderStateMixin {
  final InputManager inputManager = InputManager();
  final DoorManager doorManager = DoorManager();
  final PushwallManager pushwallManager = PushwallManager();

  late Ticker _gameLoop;
  final FocusNode _focusNode = FocusNode();
  late Level currentLevel;
  late WolfLevel activeLevel;

  final double fov = math.pi / 3;

  late Player player;

  bool _isLoading = true;

  double damageFlashOpacity = 0.0;

  late int _currentEpisodeIndex;
  late int _currentLevelIndex;
  int? _returnLevelIndex;

  List<Entity> entities = [];

  @override
  void initState() {
    super.initState();
    _initGame();
  }

  Future<void> _initGame() async {
    // 1. Setup our starting indices
    _currentEpisodeIndex = widget.startingEpisode;
    _currentLevelIndex = 0;

    // 2. Load the first floor!
    _loadLevel();

    _gameLoop = createTicker(_tick)..start();
    _focusNode.requestFocus();

    setState(() {
      _isLoading = false;
    });
  }

  void _loadLevel() {
    // 1. Clean up the previous level's state
    entities.clear();
    damageFlashOpacity = 0.0;

    // 2. Grab the exact level from our new Episode hierarchy
    final episode = widget.data.episodes[_currentEpisodeIndex];
    activeLevel = episode.levels[_currentLevelIndex];

    // 3. DEEP COPY the wall grid! If we don't do this, destroying walls/doors
    // will permanently corrupt the map data in the Wolf3d singleton.
    currentLevel = List.generate(
      64,
      (y) => List.from(activeLevel.wallGrid[y]),
    );
    final Level objectLevel = activeLevel.objectGrid;

    // 4. Initialize Managers
    doorManager.initDoors(currentLevel);
    pushwallManager.initPushwalls(currentLevel, objectLevel);

    // 5. Play Music
    Wolf3d.I.audio.playLevelMusic(activeLevel);

    // 6. Spawn Player and Entities
    for (int y = 0; y < 64; y++) {
      for (int x = 0; x < 64; x++) {
        int objId = objectLevel[y][x];

        if (!MapObject.shouldSpawn(objId, widget.difficulty)) continue;

        if (objId >= MapObject.playerNorth && objId <= MapObject.playerWest) {
          double spawnAngle = 0.0;
          if (objId == MapObject.playerNorth) {
            spawnAngle = 3 * math.pi / 2;
          } else if (objId == MapObject.playerEast) {
            spawnAngle = 0.0;
          } else if (objId == MapObject.playerSouth) {
            spawnAngle = math.pi / 2;
          } else if (objId == MapObject.playerWest) {
            spawnAngle = math.pi;
          }

          player = Player(x: x + 0.5, y: y + 0.5, angle: spawnAngle);
        } else {
          Entity? newEntity = EntityRegistry.spawn(
            objId,
            x + 0.5,
            y + 0.5,
            widget.difficulty,
            widget.data.sprites.length,
            isSharewareMode: widget.data.version == GameVersion.shareware,
          );
          if (newEntity != null) entities.add(newEntity);
        }
      }
    }

    // 7. Clear non-solid blocks from the collision grid
    for (int y = 0; y < 64; y++) {
      for (int x = 0; x < 64; x++) {
        int id = currentLevel[y][x];
        if (!((id >= 1 && id <= 63) || (id >= 90 && id <= 101))) {
          currentLevel[y][x] = 0;
        }
      }
    }

    _bumpPlayerIfStuck();
    debugPrint("Loaded Floor: ${_currentLevelIndex + 1} - ${activeLevel.name}");
  }

  void _onLevelCompleted({bool isSecretExit = false}) {
    Wolf3d.I.audio.stopMusic();

    setState(() {
      final currentEpisode = widget.data.episodes[_currentEpisodeIndex];

      if (isSecretExit) {
        // Save the next normal map index so we can return to it later
        _returnLevelIndex = _currentLevelIndex + 1;
        _currentLevelIndex = 9; // Jump to the secret map
        debugPrint("Found the Secret Exit!");
      } else {
        // Are we currently ON the secret map, and need to return?
        if (_currentLevelIndex == 9 && _returnLevelIndex != null) {
          _currentLevelIndex = _returnLevelIndex!;
          _returnLevelIndex = null;
        } else {
          _currentLevelIndex++; // Normal progression
        }
      }

      // Did we just beat the last map in the episode (Map 9) or the secret map (Map 10)?
      if (_currentLevelIndex >= currentEpisode.levels.length ||
          _currentLevelIndex > 9) {
        debugPrint("Episode Completed! You win!");
        Navigator.of(context).pop();
      } else {
        _loadLevel();
      }
    });
  }

  @override
  void dispose() {
    _gameLoop.dispose();
    _focusNode.dispose();
    super.dispose();
  }

  void _bumpPlayerIfStuck() {
    int pX = player.x.toInt();
    int pY = player.y.toInt();

    if (pY < 0 ||
        pY >= currentLevel.length ||
        pX < 0 ||
        pX >= currentLevel[0].length ||
        currentLevel[pY][pX] > 0) {
      double shortestDist = double.infinity;
      Coordinate2D nearestSafeSpot = Coordinate2D(1.5, 1.5);

      for (int y = 0; y < currentLevel.length; y++) {
        for (int x = 0; x < currentLevel[y].length; x++) {
          if (currentLevel[y][x] == 0) {
            Coordinate2D safeSpot = Coordinate2D(x + 0.5, y + 0.5);
            double dist = safeSpot.distanceTo(player.position);

            if (dist < shortestDist) {
              shortestDist = dist;
              nearestSafeSpot = safeSpot;
            }
          }
        }
      }
      player.x = nearestSafeSpot.x;
      player.y = nearestSafeSpot.y;
    }
  }

  bool _isWalkable(int x, int y) {
    if (currentLevel[y][x] == 0) return true;
    if (currentLevel[y][x] >= 90) {
      return doorManager.isDoorOpenEnough(x, y);
    }
    return false;
  }

  // --- ORCHESTRATOR ---
  void _tick(Duration elapsed) {
    // 1. Process intentions and receive movement vectors
    final inputResult = _processInputs(elapsed);

    doorManager.update(elapsed);
    pushwallManager.update(elapsed, currentLevel);

    // 2. Explicit State Updates
    player.updateWeaponSwitch();

    player.angle += inputResult.dAngle;

    // Keep the angle neatly clamped between 0 and 2*PI
    if (player.angle < 0) player.angle += 2 * math.pi;
    if (player.angle >= 2 * math.pi) player.angle -= 2 * math.pi;

    final Coordinate2D validatedPos = _calculateValidatedPosition(
      player.position,
      inputResult.movement,
    );

    player.x = validatedPos.x;
    player.y = validatedPos.y;

    _updateEntities(elapsed);

    // Explicit reassignment from a pure(r) function
    damageFlashOpacity = _calculateScreenEffects(damageFlashOpacity);

    // 3. Combat
    player.updateWeapon(
      currentTime: elapsed.inMilliseconds,
      entities: entities,
      isWalkable: _isWalkable,
    );

    // 4. Render
    setState(() {});
  }

  // Returns a Record containing both movement delta and rotation delta
  ({Coordinate2D movement, double dAngle}) _processInputs(Duration elapsed) {
    inputManager.update();

    const double moveSpeed = 0.14;
    const double turnSpeed = 0.10;

    Coordinate2D movement = const Coordinate2D(0, 0);
    double dAngle = 0.0;

    if (inputManager.requestedWeapon != null) {
      player.requestWeaponSwitch(inputManager.requestedWeapon!);
    }

    if (inputManager.isFiring) {
      player.fire(elapsed.inMilliseconds);
    } else {
      player.releaseTrigger();
    }

    // Calculate intended rotation
    if (inputManager.isTurningLeft) dAngle -= turnSpeed;
    if (inputManager.isTurningRight) dAngle += turnSpeed;

    // Calculate intended movement based on CURRENT angle
    Coordinate2D forwardVec = Coordinate2D(
      math.cos(player.angle),
      math.sin(player.angle),
    );

    if (inputManager.isMovingForward) {
      movement += forwardVec * moveSpeed;
    }
    if (inputManager.isMovingBackward) {
      movement -= forwardVec * moveSpeed;
    }

    if (inputManager.isInteracting) {
      // 1. Calculate the tile exactly 1 block in front of the player
      int targetX = (player.x + math.cos(player.angle)).toInt();
      int targetY = (player.y + math.sin(player.angle)).toInt();

      // Ensure we don't check outside the map bounds
      if (targetX >= 0 && targetX < 64 && targetY >= 0 && targetY < 64) {
        // 2. Check the WALL grid for the physical switch texture
        int wallId = currentLevel[targetY][targetX];
        if (wallId == MapObject.normalElevatorSwitch) {
          // Player hit the switch!
          _onLevelCompleted(isSecretExit: false);
          return (movement: const Coordinate2D(0, 0), dAngle: 0.0);
        } else if (wallId == MapObject.secretElevatorSwitch) {
          _onLevelCompleted(isSecretExit: true);
          return (movement: const Coordinate2D(0, 0), dAngle: 0.0);
        }

        // 3. Check the OBJECT grid for invisible floor triggers
        // (Some custom maps use these instead of wall switches)
        int objId = activeLevel.objectGrid[targetY][targetX];
        if (objId == MapObject.normalExitTrigger) {
          _onLevelCompleted(isSecretExit: false);
          return (movement: movement, dAngle: dAngle);
        } else if (objId == MapObject.secretExitTrigger) {
          _onLevelCompleted(isSecretExit: true);
          return (movement: movement, dAngle: dAngle);
        }
      }

      // 4. If it wasn't an elevator, try opening a door or pushing a wall
      doorManager.handleInteraction(player.x, player.y, player.angle);
      pushwallManager.handleInteraction(
        player.x,
        player.y,
        player.angle,
        currentLevel,
      );
    }

    return (movement: movement, dAngle: dAngle);
  }

  Coordinate2D _calculateValidatedPosition(
    Coordinate2D currentPos,
    Coordinate2D movement,
  ) {
    const double margin = 0.3;
    double newX = currentPos.x;
    double newY = currentPos.y;

    // Calculate potential new coordinates
    Coordinate2D target = currentPos + movement;

    // Validate X (allows sliding along walls)
    if (movement.x != 0) {
      int checkX = (movement.x > 0)
          ? (target.x + margin).toInt()
          : (target.x - margin).toInt();

      if (_isWalkable(checkX, currentPos.y.toInt())) {
        newX = target.x;
      }
    }

    // Validate Y
    if (movement.y != 0) {
      int checkY = (movement.y > 0)
          ? (target.y + margin).toInt()
          : (target.y - margin).toInt();

      if (_isWalkable(newX.toInt(), checkY)) {
        newY = target.y;
      }
    }

    return Coordinate2D(newX, newY);
  }

  void _updateEntities(Duration elapsed) {
    List<Entity> itemsToRemove = [];
    List<Entity> itemsToAdd = []; // NEW: Buffer for dropped items

    for (Entity entity in entities) {
      if (entity is Enemy) {
        // 1. Get Intent (Now passing tryOpenDoor!)
        final intent = entity.update(
          elapsedMs: elapsed.inMilliseconds,
          playerPosition: player.position,
          isWalkable: _isWalkable,
          tryOpenDoor: doorManager.tryOpenDoor,
          onDamagePlayer: (int damage) {
            player.takeDamage(damage);
            damageFlashOpacity = 0.5;
          },
        );

        // 2. Update Angle
        entity.angle = intent.newAngle;

        // 3. Resolve Movement
        // We NO LONGER use _calculateValidatedPosition here!
        // The enemy's internal getValidMovement already did the math perfectly.
        entity.x += intent.movement.x;
        entity.y += intent.movement.y;

        // 4. Handle Item Drops & Score (Matches KillActor in C code)
        if (entity.state == EntityState.dead &&
            entity.isDying &&
            !entity.hasDroppedItem) {
          entity.hasDroppedItem = true;

          // Map ID 44 is usually the Ammo Clip in the Object Grid/Registry
          Entity? droppedAmmo = EntityRegistry.spawn(
            MapObject.ammoClip,
            entity.x,
            entity.y,
            widget.difficulty,
            widget.data.sprites.length,
          );

          if (droppedAmmo != null) {
            itemsToAdd.add(droppedAmmo);
          }

          // You will need to add a `bool hasDroppedItem = false;` to your base Enemy class.

          if (entity.runtimeType.toString() == 'BrownGuard') {
            // Example: Spawn an ammo clip where the guard died
            // itemsToAdd.add(Collectible(x: entity.x, y: entity.y, type: CollectibleType.ammoClip));
          } else if (entity.runtimeType.toString() == 'Dog') {
            // Dogs don't drop items, but maybe they give different points!
          }
        }
      } else if (entity is Collectible) {
        if (player.position.distanceTo(entity.position) < 0.5) {
          if (player.tryPickup(entity)) {
            itemsToRemove.add(entity);
          }
        }
      }
    }

    // Clean up dead items and add new drops
    if (itemsToRemove.isNotEmpty) {
      entities.removeWhere((e) => itemsToRemove.contains(e));
    }
    if (itemsToAdd.isNotEmpty) {
      entities.addAll(itemsToAdd);
    }
  }

  // Takes an input and returns a value instead of implicitly changing state
  double _calculateScreenEffects(double currentOpacity) {
    if (currentOpacity > 0) {
      return math.max(0.0, currentOpacity - 0.05);
    }
    return currentOpacity;
  }

  @override
  Widget build(BuildContext context) {
    if (_isLoading) {
      return const Center(child: CircularProgressIndicator(color: Colors.teal));
    }

    return Scaffold(
      backgroundColor: Colors.black,
      body: KeyboardListener(
        focusNode: _focusNode,
        autofocus: true,
        onKeyEvent: (_) {},
        child: Column(
          children: [
            Expanded(
              child: LayoutBuilder(
                builder: (context, constraints) {
                  return Center(
                    child: AspectRatio(
                      aspectRatio: 16 / 10,
                      child: Stack(
                        children: [
                          CustomPaint(
                            size: Size(
                              constraints.maxWidth,
                              constraints.maxHeight,
                            ),
                            painter: RaycasterPainter(
                              map: currentLevel,
                              textures: widget.data.walls,
                              player: player,
                              fov: fov,
                              doorOffsets: doorManager.getOffsetsForRenderer(),
                              entities: entities,
                              sprites: widget.data.sprites,
                              activePushwall: pushwallManager.activePushwall,
                            ),
                          ),
                          Positioned(
                            bottom: -20,
                            left: 0,
                            right: 0,
                            child: Center(
                              child: Transform.translate(
                                offset: Offset(0, player.weaponAnimOffset),
                                child: SizedBox(
                                  width: 500,
                                  height: 500,
                                  child: CustomPaint(
                                    painter: WeaponPainter(
                                      sprite:
                                          widget.data.sprites[player
                                              .currentWeapon
                                              .getCurrentSpriteIndex(
                                                widget.data.sprites.length,
                                              )],
                                    ),
                                  ),
                                ),
                              ),
                            ),
                          ),
                          if (damageFlashOpacity > 0)
                            Positioned.fill(
                              child: Container(
                                color: Colors.red.withValues(
                                  alpha: damageFlashOpacity,
                                ),
                              ),
                            ),
                        ],
                      ),
                    ),
                  );
                },
              ),
            ),
            Hud(player: player),
          ],
        ),
      ),
    );
  }
}
