package com.zombie.game.systems;

import com.zombie.game.ecs.ECSWorld;
import com.zombie.game.ecs.System;
import com.zombie.game.ecs.components.*;
import com.zombie.game.model.GameMap;

import java.util.*;

import static com.zombie.game.model.Constants.*;

/** Zombie movement system using flow field pathfinding / 僵尸移动系统，使用流场寻路 */
public class ZombieMovementSystem implements System {

    @Override
    public void update(float dt, ECSWorld world) {
        GameMap map = world.getMap();
        if (!map.isFlowFieldValid()) return;

        List<Integer> sortedZombies = new ArrayList<>(world.getZombies());
        Collections.sort(sortedZombies);

        for (int entityId : sortedZombies) {
            Position pos = world.getPositions().get(entityId);
            Health health = world.getHealths().get(entityId);
            ZombieAI ai = world.getZombieAIs().get(entityId);

            if (pos == null || health == null || !health.isAlive()) continue;
            if (ai == null) continue;

            // Target selection: query flow field for next grid
            float centerDist = Float.MAX_VALUE;
            if (ai.isHasTarget()) {
                float dx = ai.getTargetX() - pos.getX();
                float dy = ai.getTargetY() - pos.getY();
                centerDist = (float) Math.sqrt(dx * dx + dy * dy);
            }

            if (!ai.isHasTarget() || centerDist < GRID_CENTER_THRESHOLD) {
                float[] flowDir = map.getFlowDirection(pos.getX(), pos.getY());
                float dirX = flowDir[0];
                float dirY = flowDir[1];

                if (dirX == 0 && dirY == 0) continue;

                int currentGridX = (int) Math.floor(pos.getX());
                int currentGridY = (int) Math.floor(pos.getY());
                int nextGridX = currentGridX + (int) Math.round(dirX);
                int nextGridY = currentGridY + (int) Math.round(dirY);

                // If next grid is wall, try to slide along single axis
                if (map.isWall(nextGridX, nextGridY)) {
                    nextGridX = currentGridX + (int) Math.signum(dirX);
                    nextGridY = currentGridY + (int) Math.signum(dirY);
                    if (map.isWall(nextGridX, nextGridY)) {
                        if (!map.isWall(currentGridX + (int) Math.signum(dirX), currentGridY)) {
                            nextGridX = currentGridX + (int) Math.signum(dirX);
                            nextGridY = currentGridY;
                        } else if (!map.isWall(currentGridX, currentGridY + (int) Math.signum(dirY))) {
                            nextGridX = currentGridX;
                            nextGridY = currentGridY + (int) Math.signum(dirY);
                        } else {
                            ai.setReservation(false);
                            continue;
                        }
                    }
                }

                // Check grid occupation by other zombies
                if (isGridOccupiedOrReserved(nextGridX, nextGridY, world, entityId)) {
                    int[] alt = findAlternativeDirection(currentGridX, currentGridY, dirX, dirY, map, world, entityId);
                    if (alt != null) {
                        nextGridX = alt[0];
                        nextGridY = alt[1];
                    } else {
                        ai.setReservation(false);
                        continue;
                    }
                }

                ai.setReservedGridX(nextGridX);
                ai.setReservedGridY(nextGridY);
                ai.setReservation(true);
                ai.setTargetX(nextGridX + 0.5f);
                ai.setTargetY(nextGridY + 0.5f);
                ai.setHasTarget(true);
            }

            // Move toward target
            float dx = ai.getTargetX() - pos.getX();
            float dy = ai.getTargetY() - pos.getY();
            float dist = (float) Math.sqrt(dx * dx + dy * dy);

            if (dist < 0.01f) {
                ai.setHasTarget(false);
                continue;
            }

            float moveDirX = dx / dist;
            float moveDirY = dy / dist;
            float speed = ZOMBIE_SPEED;

            float moveX = moveDirX * speed * dt;
            float moveY = moveDirY * speed * dt;
            float newX = pos.getX() + moveX;
            float newY = pos.getY() + moveY;

            boolean canMoveX = map.isWalkable(newX, pos.getY(), ZOMBIE_SIZE);
            boolean canMoveY = map.isWalkable(pos.getX(), newY, ZOMBIE_SIZE);

            if (moveX != 0 && moveY != 0) {
                // Diagonal: check corner blocking
                int checkX = (int) Math.floor(newX);
                int checkY = (int) Math.floor(newY);
                int checkCurrentX = (int) Math.floor(pos.getX());
                int checkCurrentY = (int) Math.floor(pos.getY());

                boolean blockedByCorner = false;
                if (checkX != checkCurrentX && checkY != checkCurrentY) {
                    boolean wallInX = map.isWall(checkX, checkCurrentY);
                    boolean wallInY = map.isWall(checkCurrentX, checkY);
                    if (wallInX || wallInY) {
                        blockedByCorner = true;
                        if (!wallInX && canMoveX) {
                            pos.setX(newX);
                        } else if (!wallInY && canMoveY) {
                            pos.setY(newY);
                        }
                    }
                }

                if (!blockedByCorner) {
                    boolean canMoveDiag = map.isWalkable(newX, newY, ZOMBIE_SIZE);
                    if (canMoveDiag) {
                        pos.setX(newX);
                        pos.setY(newY);
                    } else {
                        if (canMoveX) pos.setX(newX);
                        if (canMoveY) pos.setY(newY);
                    }
                }
            } else {
                if (canMoveX) pos.setX(newX);
                if (canMoveY) pos.setY(newY);
            }

            // Zombie separation
            for (int otherId : world.getZombies()) {
                if (otherId == entityId) continue;
                Health otherHealth = world.getHealths().get(otherId);
                if (otherHealth == null || !otherHealth.isAlive()) continue;
                Position otherPos = world.getPositions().get(otherId);
                if (otherPos == null) continue;

                float sepDx = pos.getX() - otherPos.getX();
                float sepDy = pos.getY() - otherPos.getY();
                float sepDist = (float) Math.sqrt(sepDx * sepDx + sepDy * sepDy);

                if (sepDist < ZOMBIE_SIZE && sepDist > 0.01f) {
                    float overlap = ZOMBIE_SIZE - sepDist;
                    float pushX = (sepDx / sepDist) * overlap * 0.5f;
                    float pushY = (sepDy / sepDist) * overlap * 0.5f;

                    if (map.isWalkable(pos.getX() + pushX, pos.getY() + pushY, ZOMBIE_SIZE)) {
                        pos.setX(pos.getX() + pushX);
                        pos.setY(pos.getY() + pushY);
                    } else {
                        if (map.isWalkable(pos.getX() + pushX, pos.getY(), ZOMBIE_SIZE)) {
                            pos.setX(pos.getX() + pushX);
                        }
                        if (map.isWalkable(pos.getX(), pos.getY() + pushY, ZOMBIE_SIZE)) {
                            pos.setY(pos.getY() + pushY);
                        }
                    }
                }
            }

            // Update facing angle
            if (moveDirX != 0 || moveDirY != 0) {
                pos.setAngle((float) Math.atan2(moveDirX, moveDirY));
            }
        }
    }

    private boolean isGridOccupiedOrReserved(int gridX, int gridY, ECSWorld world, int selfId) {
        for (int otherId : world.getZombies()) {
            if (otherId == selfId) continue;
            Health otherHealth = world.getHealths().get(otherId);
            if (otherHealth == null || !otherHealth.isAlive()) continue;

            Position otherPos = world.getPositions().get(otherId);
            if (otherPos == null) continue;

            int otherGridX = (int) Math.floor(otherPos.getX());
            int otherGridY = (int) Math.floor(otherPos.getY());
            if (otherGridX == gridX && otherGridY == gridY) return true;

            ZombieAI otherAi = world.getZombieAIs().get(otherId);
            if (otherAi != null && otherAi.isReservation() &&
                otherAi.getReservedGridX() == gridX && otherAi.getReservedGridY() == gridY) {
                return true;
            }
        }
        return false;
    }

    private int[] findAlternativeDirection(int currentGridX, int currentGridY, float dirX, float dirY,
                                            GameMap map, ECSWorld world, int selfId) {
        int[][] allDirs = {{0, -1}, {0, 1}, {-1, 0}, {1, 0}, {-1, -1}, {1, -1}, {-1, 1}, {1, 1}};
        List<int[]> candidates = new ArrayList<>();

        for (int[] dir : allDirs) {
            int nx = currentGridX + dir[0];
            int ny = currentGridY + dir[1];

            if (map.isWall(nx, ny)) continue;

            if (dir[0] != 0 && dir[1] != 0) {
                if (map.isWall(currentGridX + dir[0], currentGridY) ||
                    map.isWall(currentGridX, currentGridY + dir[1])) {
                    continue;
                }
            }

            if (isGridOccupiedOrReserved(nx, ny, world, selfId)) continue;

            float dotProduct = dir[0] * dirX + dir[1] * dirY;
            candidates.add(new int[]{nx, ny, (int) (dotProduct * 1000)});
        }

        if (candidates.isEmpty()) return null;
        candidates.sort((a, b) -> b[2] - a[2]);
        return new int[]{candidates.get(0)[0], candidates.get(0)[1]};
    }
}
