import { describe, it, expect } from 'vitest';
import {
  cellKey, parseCellKey, worldToCell, gridDims, tokenCells,
  gridDistance, toFeet,
  brushCells, rectCells, pointInPolygon, polygonCells, circleCells, coneCells, lineCells, squareCells,
  applyReveal, applyHide, revealAll, hideAll,
  type GridSpec,
} from './index';

const grid: GridSpec = { gridSize: 50, feetPerCell: 5, cols: 10, rows: 10 };

describe('grid', () => {
  it('round-trips cell keys and converts world→cell', () => {
    expect(cellKey(2, 3)).toBe('2,3');
    expect(parseCellKey('4,7')).toEqual({ col: 4, row: 7 });
    expect(worldToCell({ x: 120, y: 40 }, 50)).toEqual({ col: 2, row: 0 });
  });
  it('computes grid dims and NxN token footprints', () => {
    expect(gridDims(500, 300, 50)).toEqual({ cols: 10, rows: 6 });
    expect(tokenCells({ col: 1, row: 1, size: 2 }).sort()).toEqual(['1,1', '1,2', '2,1', '2,2'].sort());
  });
});

describe('distance', () => {
  it('5e Chebyshev, pf2e 5-10-5, euclidean', () => {
    const a = { col: 0, row: 0 };
    expect(gridDistance(a, { col: 3, row: 0 }, '5e')).toBe(3);
    expect(gridDistance(a, { col: 3, row: 3 }, '5e')).toBe(3); // diagonals free
    expect(gridDistance(a, { col: 2, row: 2 }, 'pf2e')).toBe(3); // 1 + 2 (second diagonal costs 2)
    expect(gridDistance(a, { col: 3, row: 4 }, 'euclid')).toBe(5);
    expect(toFeet(3, 5)).toBe(15);
  });
});

describe('reveal/hide tools', () => {
  it('brush covers a square block, clamped to bounds', () => {
    expect(brushCells(0, 0, 1, 10, 10).sort()).toEqual(['0,0', '0,1', '1,0', '1,1'].sort());
  });
  it('rect is inclusive', () => {
    expect(rectCells({ col: 0, row: 0 }, { col: 1, row: 1 }, 10, 10).sort())
      .toEqual(['0,0', '0,1', '1,0', '1,1'].sort());
  });
  it('polygon point-in + rasterize', () => {
    const square = [{ x: 0, y: 0 }, { x: 100, y: 0 }, { x: 100, y: 100 }, { x: 0, y: 100 }];
    expect(pointInPolygon({ x: 50, y: 50 }, square)).toBe(true);
    expect(pointInPolygon({ x: 150, y: 50 }, square)).toBe(false);
    // cells whose centers (25,25 / 75,25 / 25,75 / 75,75) fall inside
    expect(polygonCells(square, grid).sort()).toEqual(['0,0', '0,1', '1,0', '1,1'].sort());
  });
});

describe('AoE templates', () => {
  it('circle covers cells within radius', () => {
    const cells = circleCells({ x: 25, y: 25 }, 5, grid); // 5ft = 1 cell radius
    expect(cells).toContain('0,0');
    expect(cells).not.toContain('5,5');
  });
  it('square, cone, line return non-empty within bounds', () => {
    expect(squareCells({ x: 100, y: 100 }, 15, grid).length).toBeGreaterThan(0);
    expect(coneCells({ x: 0, y: 0 }, { x: 100, y: 0 }, 15, 53, grid)).toContain('1,0');
    expect(lineCells({ x: 0, y: 25 }, { x: 250, y: 25 }, 5, grid)).toContain('0,0');
  });
});

describe('fog set algebra', () => {
  it('reveal dedupes, hide removes, all/none', () => {
    expect(applyReveal(['0,0'], ['0,0', '1,1']).sort()).toEqual(['0,0', '1,1'].sort());
    expect(applyHide(['0,0', '1,1'], ['0,0'])).toEqual(['1,1']);
    expect(revealAll(2, 2).sort()).toEqual(['0,0', '0,1', '1,0', '1,1'].sort());
    expect(hideAll()).toEqual([]);
  });
});
