'use strict';

/* ────────────────────────────────────────────────────────────────
   Pathfinding — A*, grid-based navigation, flow fields
──────────────────────────────────────────────────────────────── */
const SQRT2 = Math.SQRT2;
function heuristic(ax, ay, bx, by, diagonal) {
    if (diagonal) {
        // Octile distance
        const dx = Math.abs(ax - bx);
        const dy = Math.abs(ay - by);
        return dx + dy + (SQRT2 - 2) * Math.min(dx, dy);
    }
    return Math.abs(ax - bx) + Math.abs(ay - by);
}
function findPath(grid, startX, startY, endX, endY, options) {
    var _a, _b, _c;
    const diag = (_a = options === null || options === void 0 ? void 0 : options.allowDiagonals) !== null && _a !== void 0 ? _a : true;
    const maxIter = (_b = options === null || options === void 0 ? void 0 : options.maxIterations) !== null && _b !== void 0 ? _b : 2000;
    const weight = (_c = options === null || options === void 0 ? void 0 : options.heuristicWeight) !== null && _c !== void 0 ? _c : 1;
    if (!grid.isWalkable(endX, endY))
        return null;
    const key = (x, y) => y * grid.width + x;
    const start = {
        x: startX, y: startY,
        g: 0,
        h: heuristic(startX, startY, endX, endY, diag) * weight,
        f: 0,
        parent: null,
    };
    start.f = start.g + start.h;
    // Simple binary-heap-backed open set
    const open = [start];
    const closed = new Set();
    const gScores = new Map();
    gScores.set(key(startX, startY), 0);
    const neighbors = diag
        ? [[-1, 0, 1], [1, 0, 1], [0, -1, 1], [0, 1, 1], [-1, -1, SQRT2], [1, -1, SQRT2], [-1, 1, SQRT2], [1, 1, SQRT2]]
        : [[-1, 0, 1], [1, 0, 1], [0, -1, 1], [0, 1, 1]];
    let iterations = 0;
    while (open.length > 0 && iterations < maxIter) {
        iterations++;
        // Find node with lowest f
        let bestIdx = 0;
        for (let i = 1; i < open.length; i++) {
            if (open[i].f < open[bestIdx].f)
                bestIdx = i;
        }
        const current = open[bestIdx];
        open[bestIdx] = open[open.length - 1];
        open.pop();
        if (current.x === endX && current.y === endY) {
            return reconstructPath(current);
        }
        const currentKey = key(current.x, current.y);
        closed.add(currentKey);
        for (const [dx, dy, baseCost] of neighbors) {
            const nx = current.x + dx;
            const ny = current.y + dy;
            if (nx < 0 || ny < 0 || nx >= grid.width || ny >= grid.height)
                continue;
            if (!grid.isWalkable(nx, ny))
                continue;
            const nKey = key(nx, ny);
            if (closed.has(nKey))
                continue;
            // Diagonal corner-cutting check
            if (dx !== 0 && dy !== 0) {
                if (!grid.isWalkable(current.x + dx, current.y) || !grid.isWalkable(current.x, current.y + dy)) {
                    continue;
                }
            }
            const moveCost = grid.cost ? grid.cost(nx, ny) : 1;
            const tentativeG = current.g + baseCost * moveCost;
            const prevG = gScores.get(nKey);
            if (prevG !== undefined && tentativeG >= prevG)
                continue;
            gScores.set(nKey, tentativeG);
            const node = {
                x: nx, y: ny,
                g: tentativeG,
                h: heuristic(nx, ny, endX, endY, diag) * weight,
                f: 0,
                parent: current,
            };
            node.f = node.g + node.h;
            open.push(node);
        }
    }
    return null; // No path found
}
function reconstructPath(node) {
    const path = [];
    let current = node;
    while (current) {
        path.push({ x: current.x, y: current.y });
        current = current.parent;
    }
    path.reverse();
    return path;
}
/* ── Path Smoothing ───────────────────────────────────────────── */
function smoothPath(path, grid) {
    if (path.length <= 2)
        return path;
    const result = [path[0]];
    let current = 0;
    while (current < path.length - 1) {
        let furthest = current + 1;
        for (let i = path.length - 1; i > current + 1; i--) {
            if (hasLineOfSight(grid, path[current], path[i])) {
                furthest = i;
                break;
            }
        }
        result.push(path[furthest]);
        current = furthest;
    }
    return result;
}
function hasLineOfSight(grid, a, b) {
    // Bresenham
    let x0 = a.x, y0 = a.y;
    const x1 = b.x, y1 = b.y;
    const dx = Math.abs(x1 - x0);
    const dy = Math.abs(y1 - y0);
    const sx = x0 < x1 ? 1 : -1;
    const sy = y0 < y1 ? 1 : -1;
    let err = dx - dy;
    while (true) {
        if (!grid.isWalkable(x0, y0))
            return false;
        if (x0 === x1 && y0 === y1)
            break;
        const e2 = 2 * err;
        if (e2 > -dy) {
            err -= dy;
            x0 += sx;
        }
        if (e2 < dx) {
            err += dx;
            y0 += sy;
        }
    }
    return true;
}
/* ── Flow Field ───────────────────────────────────────────────── */
class FlowField {
    constructor(width, height) {
        this.width = width;
        this.height = height;
        this.directions = new Array(width * height).fill(null).map(() => ({ x: 0, y: 0 }));
        this.costs = new Float32Array(width * height).fill(Infinity);
    }
    /** Generate flow field towards target (tileX, tileY). */
    generate(grid, targetX, targetY) {
        const w = this.width;
        const idx = (x, y) => y * w + x;
        // Reset
        this.costs.fill(Infinity);
        for (const d of this.directions) {
            d.x = 0;
            d.y = 0;
        }
        // Dijkstra pass
        const targetIdx = idx(targetX, targetY);
        this.costs[targetIdx] = 0;
        const queue = [[targetX, targetY]];
        const neighbors = [[-1, 0], [1, 0], [0, -1], [0, 1]];
        while (queue.length > 0) {
            const [cx, cy] = queue.shift();
            const currentCost = this.costs[idx(cx, cy)];
            for (const [dx, dy] of neighbors) {
                const nx = cx + dx;
                const ny = cy + dy;
                if (nx < 0 || ny < 0 || nx >= w || ny >= this.height)
                    continue;
                if (!grid.isWalkable(nx, ny))
                    continue;
                const moveCost = grid.cost ? grid.cost(nx, ny) : 1;
                const newCost = currentCost + moveCost;
                const nIdx = idx(nx, ny);
                if (newCost < this.costs[nIdx]) {
                    this.costs[nIdx] = newCost;
                    queue.push([nx, ny]);
                }
            }
        }
        // Direction pass — each cell points toward lowest-cost neighbor
        for (let y = 0; y < this.height; y++) {
            for (let x = 0; x < w; x++) {
                const i = idx(x, y);
                if (this.costs[i] === Infinity)
                    continue;
                if (x === targetX && y === targetY)
                    continue;
                let bestCost = this.costs[i];
                let bx = 0, by = 0;
                for (const [dx, dy] of [[-1, 0], [1, 0], [0, -1], [0, 1], [-1, -1], [1, -1], [-1, 1], [1, 1]]) {
                    const nx = x + dx;
                    const ny = y + dy;
                    if (nx < 0 || ny < 0 || nx >= w || ny >= this.height)
                        continue;
                    const c = this.costs[idx(nx, ny)];
                    if (c < bestCost) {
                        bestCost = c;
                        bx = dx;
                        by = dy;
                    }
                }
                const len = Math.sqrt(bx * bx + by * by) || 1;
                this.directions[i].x = bx / len;
                this.directions[i].y = by / len;
            }
        }
    }
    getDirection(tileX, tileY) {
        if (tileX < 0 || tileY < 0 || tileX >= this.width || tileY >= this.height) {
            return { x: 0, y: 0 };
        }
        return this.directions[tileY * this.width + tileX];
    }
    getCost(tileX, tileY) {
        if (tileX < 0 || tileY < 0 || tileX >= this.width || tileY >= this.height) {
            return Infinity;
        }
        return this.costs[tileY * this.width + tileX];
    }
}
/** Find which polygon contains a point (simple point-in-polygon test). */
function findContainingPolygon(mesh, point) {
    for (const poly of mesh.polygons) {
        if (isPointInConvexPolygon(point, poly.vertices)) {
            return poly;
        }
    }
    return null;
}
function isPointInConvexPolygon(point, vertices) {
    const n = vertices.length;
    for (let i = 0; i < n; i++) {
        const a = vertices[i];
        const b = vertices[(i + 1) % n];
        const cross = (b.x - a.x) * (point.y - a.y) - (b.y - a.y) * (point.x - a.x);
        if (cross < 0)
            return false;
    }
    return true;
}

exports.FlowField = FlowField;
exports.findContainingPolygon = findContainingPolygon;
exports.findPath = findPath;
exports.smoothPath = smoothPath;
//# sourceMappingURL=Pathfinding.js.map
