package se.urmo.game.entities.ghost.mode;

import lombok.extern.slf4j.Slf4j;
import se.urmo.game.entities.ghost.Ghost;
import se.urmo.game.entities.ghost.strategy.GhostStrategy;
import se.urmo.game.entities.pacman.PacMan;
import se.urmo.game.graphics.SpriteLocation;
import se.urmo.game.graphics.SpriteSheetManager;
import se.urmo.game.main.LevelManager;
import se.urmo.game.map.GameMap;
import se.urmo.game.util.Direction;
import se.urmo.game.util.MyPoint;

import java.awt.Point;
import java.awt.image.BufferedImage;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Slf4j
public abstract class AbstractGhostModeImpl implements GhostState {
    private final Ghost ghost;
    protected GhostStrategy strategy;
    protected final LevelManager levelManager;
    protected int animation;

    public AbstractGhostModeImpl(Ghost ghost, GhostStrategy strategy, LevelManager levelManager, int animation) {
        this.ghost = ghost;
        this.strategy = strategy;
        this.levelManager = levelManager;
        this.animation = animation;
    }

    /**
     * Update method to be implemented by each mode
     */
    public abstract void update(Ghost ghost, PacMan pacman, GameMap map);

    public double getSpeed() {
        return Ghost.BASE_SPEED * levelManager.getGhostSpeed();
    }

    @Override
    public MyPoint getPosition() {
        return ghost.getPosition();
    }

    @Override
    public Direction getDirection() {
        return ghost.getDirection();
    }

    @Override
    public BufferedImage[] getAnimation() {
        return SpriteSheetManager.get(SpriteLocation.GHOST).getAnimation(animation);
    }

    protected void updatePosition(Ghost ghost, PacMan pacman, GameMap map) {
        if (map.isAligned(getPosition().asPoint())) {
            ghost.setPrevDirection(getDirection());
            ghost.setDirection(chooseDirection(
                    ghost,
                    prioritizeDirections(
                            ghost.getCollisionChecker().calculateDirectionAlternatives(ghost, getPosition())),
                    getStrategy().chooseTarget(ghost, pacman, map)));

            log.debug("Ghost moving to {}", getPosition());
        }

        moveTo(ghost, getNewPosition(getSpeed()));
    }

    protected GhostStrategy getStrategy() {
        return strategy;
    }

    private MyPoint getNewPosition(double speed) {
        MyPoint position = ghost.getPosition();
        Direction direction = ghost.getDirection();
        return new MyPoint(
                position.x + direction.dx * speed,
                position.y + direction.dy * speed);
    }

    private void moveTo(Ghost ghost, MyPoint newPosition) {
        MyPoint destination = ghost.getCollisionChecker().canMoveTo(ghost,
                getDirection(), newPosition.x, newPosition.y);
        if (destination != null) {
            ghost.setPosition(destination);
        }
    }

    private Map<Direction, Integer> prioritizeDirections(List<Direction> directions) {
        return directions.stream()
                .filter(d -> d != Direction.NONE)
                .collect(Collectors.toMap(
                        d -> d,
                        d -> (ghost.getPrevDirection() != null &&
                                d == ghost.getPrevDirection().opposite()) ? 2 : 1
                ));
    }

    private Direction chooseDirection(Ghost ghost, Map<Direction, Integer> options, Point target) {
        // Find the lowest priority
        int lowestPriority = options.values().stream()
                .mapToInt(Integer::intValue)
                .min()
                .orElse(Integer.MAX_VALUE);

        // Collect all directions that have this priority
        List<Direction> directions = options.entrySet().stream()
                .filter(entry -> entry.getValue() == lowestPriority)
                .map(Map.Entry::getKey)
                .toList();

        MyPoint position = getPosition();
        // Create a record to hold direction and distance
        record DirectionDistance(Direction direction, double distance) {
        }

        // Stream through directions and find the one with the minimum distance to the target
        List<DirectionDistance> dd = directions.stream()
                .map(d -> {
                    double nx = position.x + d.dx * GameMap.MAP_TILESIZE;
                    double ny = position.y + d.dy * GameMap.MAP_TILESIZE;
                    double dist = target.distance(nx, ny);
                    return new DirectionDistance(d, dist);
                })
                .toList();

        log.debug("Target: {}, Position: {}", target, getPosition());
        log.debug("Directions: {}", dd);

        Direction best = dd.stream()
                .min(Comparator.comparingDouble(DirectionDistance::distance))
                .map(DirectionDistance::direction)
                .orElse(directions.getFirst()); // Fallback to first direction if stream is empty

        log.debug("Ghost coming from {}, choosing {}, from {}",
                ghost.getPrevDirection(), best, directions);
        return best;
    }

    public void resetPosition() {
        ghost.setPosition(Ghost.getStartPosition());
    }
}