import { describe, it, expect } from "bun:test";
import { bresenhamLine } from "@common/navigation/bresenham";

// Helpers
const pts = (coords: [number, number][]) =>
    coords.map(([x, y]) => ({ x, y }));

function is4Connected(points: { x: number; y: number }[]): boolean {
    for (let i = 1; i < points.length; i++) {
        const dx = Math.abs(points[i].x - points[i - 1].x);
        const dy = Math.abs(points[i].y - points[i - 1].y);
        if (dx + dy !== 1) return false;
    }
    return true;
}

function is8Connected(points: { x: number; y: number }[]): boolean {
    for (let i = 1; i < points.length; i++) {
        const dx = Math.abs(points[i].x - points[i - 1].x);
        const dy = Math.abs(points[i].y - points[i - 1].y);
        if (dx > 1 || dy > 1) return false;
    }
    return true;
}

describe("bresenham", () => {
    describe("single point", () => {
        it("returns one point when start === end", () => {
            expect(bresenhamLine(3, 3, 3, 3)).toEqual(pts([[3, 3]]));
        });
    });

    describe("axis-aligned lines", () => {
        it("horizontal right", () => {
            expect(bresenhamLine(0, 0, 3, 0)).toEqual(pts([[0, 0], [1, 0], [2, 0], [3, 0]]));
        });

        it("horizontal left", () => {
            expect(bresenhamLine(3, 0, 0, 0)).toEqual(pts([[3, 0], [2, 0], [1, 0], [0, 0]]));
        });

        it("vertical down", () => {
            expect(bresenhamLine(0, 0, 0, 3)).toEqual(pts([[0, 0], [0, 1], [0, 2], [0, 3]]));
        });

        it("vertical up", () => {
            expect(bresenhamLine(0, 3, 0, 0)).toEqual(pts([[0, 3], [0, 2], [0, 1], [0, 0]]));
        });
    });

    describe("diagonal lines", () => {
        it("perfect diagonal — directions=4 splits into axis steps", () => {
            const result = bresenhamLine(0, 0, 2, 2);
            expect(result[0]).toEqual({ x: 0, y: 0 });
            expect(result[result.length - 1]).toEqual({ x: 2, y: 2 });
            expect(is4Connected(result)).toBe(true);
            // dx+dy+1 = 2+2+1 = 5 points
            expect(result.length).toBe(5);
        });

        it("perfect diagonal — directions=8 emits single diagonal steps", () => {
            const result = bresenhamLine(0, 0, 2, 2, { directions: 8 });
            expect(result).toEqual(pts([[0, 0], [1, 1], [2, 2]]));
        });

        it("diagonal all quadrants produce correct endpoints", () => {
            const cases: [[number, number, number, number]] = [
                [0, 0, 3, 3],
                [0, 0, -3, 3],
                [0, 0, 3, -3],
                [0, 0, -3, -3],
            ] as any;
            for (const [fx, fy, tx, ty] of cases) {
                const r = bresenhamLine(fx, fy, tx, ty, { directions: 8 });
                expect(r[0]).toEqual({ x: fx, y: fy });
                expect(r[r.length - 1]).toEqual({ x: tx, y: ty });
                expect(is8Connected(r)).toBe(true);
            }
        });
    });

    describe("connectivity guarantees", () => {
        it("directions=4 (default) is always 4-connected", () => {
            // steep slope
            expect(is4Connected(bresenhamLine(0, 0, 3, 7))).toBe(true);
            // shallow slope
            expect(is4Connected(bresenhamLine(0, 0, 7, 3))).toBe(true);
            // negative direction
            expect(is4Connected(bresenhamLine(5, 5, -2, 1))).toBe(true);
        });

        it("directions=8 is always 8-connected", () => {
            expect(is8Connected(bresenhamLine(0, 0, 3, 7, { directions: 8 }))).toBe(true);
            expect(is8Connected(bresenhamLine(0, 0, 7, 3, { directions: 8 }))).toBe(true);
            expect(is8Connected(bresenhamLine(5, 5, -2, 1, { directions: 8 }))).toBe(true);
        });

        it("directions=4 output length is dx+dy+1", () => {
            const r = bresenhamLine(0, 0, 4, 3);
            expect(r.length).toBe(4 + 3 + 1);
        });

        it("directions=8 output length is max(dx,dy)+1", () => {
            const r = bresenhamLine(0, 0, 4, 3, { directions: 8 });
            expect(r.length).toBe(Math.max(4, 3) + 1);
        });

        it("start and end are always first and last points", () => {
            const r = bresenhamLine(1, 2, 5, 8);
            expect(r[0]).toEqual({ x: 1, y: 2 });
            expect(r[r.length - 1]).toEqual({ x: 5, y: 8 });
        });
    });

    describe("clipping", () => {
        it("returns empty array when segment is entirely outside bounds", () => {
            expect(bresenhamLine(10, 10, 20, 20, { minX: 0, maxX: 5, minY: 0, maxY: 5 })).toEqual([]);
        });

        it("clips start when it lies outside bounds", () => {
            const r = bresenhamLine(-5, 0, 5, 0, { minX: 0, maxX: 10, minY: 0, maxY: 10 });
            expect(r[0].x).toBeGreaterThanOrEqual(0);
            expect(r[r.length - 1]).toEqual({ x: 5, y: 0 });
        });

        it("clips end when it lies outside bounds", () => {
            const r = bresenhamLine(0, 0, 15, 0, { minX: 0, maxX: 10, minY: 0, maxY: 10 });
            expect(r[0]).toEqual({ x: 0, y: 0 });
            expect(r[r.length - 1].x).toBeLessThanOrEqual(10);
        });

        it("all returned points are within bounds", () => {
            const bounds = { minX: 1, maxX: 8, minY: 1, maxY: 8 };
            const r = bresenhamLine(0, 0, 10, 10, bounds);
            for (const p of r) {
                expect(p.x).toBeGreaterThanOrEqual(bounds.minX);
                expect(p.x).toBeLessThanOrEqual(bounds.maxX);
                expect(p.y).toBeGreaterThanOrEqual(bounds.minY);
                expect(p.y).toBeLessThanOrEqual(bounds.maxY);
            }
        });

        it("segment touching only a corner of bounds returns at least one point", () => {
            // line passes through (0,0) exactly, bounds include only (0,0)
            const r = bresenhamLine(-2, -2, 2, 2, { minX: 0, maxX: 0, minY: 0, maxY: 0 });
            expect(r.length).toBeGreaterThan(0);
        });

        it("clipping preserves 4-connectivity within bounds", () => {
            const r = bresenhamLine(-3, -3, 10, 10, { minX: 0, maxX: 6, minY: 0, maxY: 6 });
            expect(is4Connected(r)).toBe(true);
        });

        it("clipping preserves 8-connectivity within bounds", () => {
            const r = bresenhamLine(-3, -3, 10, 10, { minX: 0, maxX: 6, minY: 0, maxY: 6, directions: 8 });
            expect(is8Connected(r)).toBe(true);
        });
    });

    describe("non-integer inputs", () => {
        it("rounds float inputs to nearest integer", () => {
            expect(bresenhamLine(0.4, 0.4, 2.6, 0.4)).toEqual(pts([[0, 0], [1, 0], [2, 0], [3, 0]]));
        });
    });
});
