package com.zombie.game.model;

import com.zombie.game.ecs.components.BlocksMovement;
import com.zombie.game.ecs.components.MovementCost;

import java.util.*;

/** Flow field navigation using Dijkstra with ECS component-based wall queries / 基于ECS组件查询的Dijkstra流场导航 */
public class FlowField {
    private final int width;
    private final int height;
    private float[][] distanceField;
    private float[][] flowFieldX;
    private float[][] flowFieldY;
    private boolean valid;

    public FlowField(int width, int height) {
        this.width = width;
        this.height = height;
        this.distanceField = new float[height][width];
        this.flowFieldX = new float[height][width];
        this.flowFieldY = new float[height][width];
        this.valid = false;
    }

    /**
     * Update flow field based on player positions and ECS wall components.
     * @param wallGrid spatial index "x,y" -> entityId
     * @param blocksMovements blocks-movement component map
     * @param movementCosts movement-cost component map
     * @param playerPositions list of [x, y] player positions
     */
    public void update(Map<String, Integer> wallGrid,
                       Map<Integer, BlocksMovement> blocksMovements,
                       Map<Integer, MovementCost> movementCosts,
                       List<float[]> playerPositions) {
        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++) {
                distanceField[y][x] = Float.MAX_VALUE;
            }
        }

        PriorityQueue<Node> queue = new PriorityQueue<>(Comparator.comparingDouble(n -> n.dist));

        for (float[] pos : playerPositions) {
            int px = (int) Math.floor(pos[0]);
            int py = (int) Math.floor(pos[1]);
            if (px >= 0 && px < width && py >= 0 && py < height) {
                if (!isBlocked(wallGrid, blocksMovements, px, py)) {
                    distanceField[py][px] = 0;
                    queue.add(new Node(px, py, 0));
                }
            }
        }

        int[][] dirs = {{0, -1}, {0, 1}, {-1, 0}, {1, 0}, {-1, -1}, {1, -1}, {-1, 1}, {1, 1}};

        while (!queue.isEmpty()) {
            Node current = queue.poll();
            int cx = current.x;
            int cy = current.y;
            float currentDist = current.dist;

            if (currentDist > distanceField[cy][cx]) continue;

            for (int[] dir : dirs) {
                int nx = cx + dir[0];
                int ny = cy + dir[1];

                if (nx < 0 || nx >= width || ny < 0 || ny >= height) continue;

                // Diagonal: check corner blocking
                if (dir[0] != 0 && dir[1] != 0) {
                    if (isBlocked(wallGrid, blocksMovements, cx + dir[0], cy) ||
                        isBlocked(wallGrid, blocksMovements, cx, cy + dir[1])) {
                        continue;
                    }
                }

                float moveCost = getMovementCost(wallGrid, blocksMovements, movementCosts, nx, ny, dir);
                if (moveCost == Float.MAX_VALUE) continue;

                float newDist = currentDist + moveCost;

                if (newDist < distanceField[ny][nx]) {
                    distanceField[ny][nx] = newDist;
                    queue.add(new Node(nx, ny, newDist));
                }
            }
        }

        // Compute flow directions
        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++) {
                if (isBlocked(wallGrid, blocksMovements, x, y) || distanceField[y][x] == Float.MAX_VALUE) {
                    flowFieldX[y][x] = 0;
                    flowFieldY[y][x] = 0;
                    continue;
                }

                float bestDist = distanceField[y][x];
                float bestDirX = 0;
                float bestDirY = 0;

                for (int[] dir : dirs) {
                    int nx = x + dir[0];
                    int ny = y + dir[1];

                    if (nx < 0 || nx >= width || ny < 0 || ny >= height) continue;
                    if (isBlocked(wallGrid, blocksMovements, nx, ny)) continue;

                    if (dir[0] != 0 && dir[1] != 0) {
                        if (isBlocked(wallGrid, blocksMovements, x + dir[0], y) ||
                            isBlocked(wallGrid, blocksMovements, x, y + dir[1])) {
                            continue;
                        }
                    }

                    if (distanceField[ny][nx] < bestDist) {
                        bestDist = distanceField[ny][nx];
                        float len = (float) Math.sqrt(dir[0] * dir[0] + dir[1] * dir[1]);
                        bestDirX = dir[0] / len;
                        bestDirY = dir[1] / len;
                    }
                }

                flowFieldX[y][x] = bestDirX;
                flowFieldY[y][x] = bestDirY;
            }
        }

        valid = true;
    }

    /** Get flow direction at world position / 获取世界坐标处的流场方向 */
    public float[] getDirection(float wx, float wy) {
        int gx = (int) Math.floor(wx);
        int gy = (int) Math.floor(wy);
        if (gx < 0 || gx >= width || gy < 0 || gy >= height) {
            return new float[]{0, 0};
        }
        return new float[]{flowFieldX[gy][gx], flowFieldY[gy][gx]};
    }

    /** Get distance to nearest player at world position / 获取世界坐标处到最近玩家的距离 */
    public float getDistance(float wx, float wy) {
        int gx = (int) Math.floor(wx);
        int gy = (int) Math.floor(wy);
        if (gx < 0 || gx >= width || gy < 0 || gy >= height) {
            return Float.MAX_VALUE;
        }
        return distanceField[gy][gx];
    }

    public boolean isValid() { return valid; }
    public void invalidate() { valid = false; }

    private boolean isBlocked(Map<String, Integer> wallGrid,
                              Map<Integer, BlocksMovement> blocksMovements,
                              int gx, int gy) {
        if (gx < 0 || gx >= width || gy < 0 || gy >= height) return true;
        Integer entityId = wallGrid.get(gx + "," + gy);
        if (entityId == null) return false;
        return blocksMovements.containsKey(entityId);
    }

    private float getMovementCost(Map<String, Integer> wallGrid,
                                  Map<Integer, BlocksMovement> blocksMovements,
                                  Map<Integer, MovementCost> movementCosts,
                                  int gx, int gy, int[] dir) {
        float baseCost = (dir[0] != 0 && dir[1] != 0) ? 1.414f : 1.0f;
        Integer entityId = wallGrid.get(gx + "," + gy);
        if (entityId == null) return baseCost;
        // Completely blocked (no MovementCost but has BlocksMovement)
        if (blocksMovements.containsKey(entityId) && !movementCosts.containsKey(entityId)) {
            return Float.MAX_VALUE;
        }
        MovementCost mc = movementCosts.get(entityId);
        if (mc != null) {
            return baseCost + mc.getCost();
        }
        return baseCost;
    }

    private static class Node {
        int x, y;
        float dist;
        Node(int x, int y, float dist) {
            this.x = x;
            this.y = y;
            this.dist = dist;
        }
    }
}
