/**
 * Dungeon Utilities - Constants, Types, and Noise Functions
 * Ported from web/src/app/routes/wall-gen/tileUtils.ts for TUI game
 */

// =============================================================================
// CONSTANTS
// =============================================================================

export const DUNGEON_TILE_SIZE = 16;
export const DUNGEON_MAP_WIDTH = 100;
export const DUNGEON_MAP_HEIGHT = 100;

// Cell types
export const CELL_FLOOR = 0;
export const CELL_WALL = 1;

export type CellType = typeof CELL_FLOOR | typeof CELL_WALL;
export type TileCoords = [col: number, row: number];
export type TileGrid = CellType[][];

// =============================================================================
// SEEDED RANDOM NUMBER GENERATOR
// =============================================================================

export class SeededRandom {
  private seed: number;

  constructor(seed: number) {
    this.seed = seed;
  }

  next(): number {
    let t = (this.seed += 0x6d2b79f5);
    t = Math.imul(t ^ (t >>> 15), t | 1);
    t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
  }

  nextInt(min: number, max: number): number {
    return Math.floor(this.next() * (max - min + 1)) + min;
  }

  nextFloat(min: number, max: number): number {
    return this.next() * (max - min) + min;
  }

  nextBool(p: number = 0.5): boolean {
    return this.next() < p;
  }

  shuffle<T>(array: T[]): T[] {
    const result = [...array];
    for (let i = result.length - 1; i > 0; i--) {
      const j = Math.floor(this.next() * (i + 1));
      [result[i], result[j]] = [result[j], result[i]];
    }
    return result;
  }

  pick<T>(array: T[]): T {
    return array[Math.floor(this.next() * array.length)];
  }
}

// =============================================================================
// SIMPLE NOISE IMPLEMENTATION (no external dependencies)
// =============================================================================

// Simple seeded noise implementation
let noiseSeed = 12345;
let noisePermutation: number[] = [];

function initNoisePermutation(seed: number): void {
  const rng = new SeededRandom(seed);
  noisePermutation = [];
  for (let i = 0; i < 256; i++) {
    noisePermutation[i] = i;
  }
  // Shuffle
  for (let i = 255; i > 0; i--) {
    const j = Math.floor(rng.next() * (i + 1));
    [noisePermutation[i], noisePermutation[j]] = [noisePermutation[j], noisePermutation[i]];
  }
  // Duplicate for overflow
  for (let i = 0; i < 256; i++) {
    noisePermutation[256 + i] = noisePermutation[i];
  }
}

function fade(t: number): number {
  return t * t * t * (t * (t * 6 - 15) + 10);
}

function lerp(a: number, b: number, t: number): number {
  return a + t * (b - a);
}

function grad(hash: number, x: number, y: number): number {
  const h = hash & 3;
  const u = h < 2 ? x : y;
  const v = h < 2 ? y : x;
  return ((h & 1) === 0 ? u : -u) + ((h & 2) === 0 ? v : -v);
}

function perlin2D(x: number, y: number): number {
  const X = Math.floor(x) & 255;
  const Y = Math.floor(y) & 255;

  const xf = x - Math.floor(x);
  const yf = y - Math.floor(y);

  const u = fade(xf);
  const v = fade(yf);

  const aa = noisePermutation[noisePermutation[X] + Y];
  const ab = noisePermutation[noisePermutation[X] + Y + 1];
  const ba = noisePermutation[noisePermutation[X + 1] + Y];
  const bb = noisePermutation[noisePermutation[X + 1] + Y + 1];

  const x1 = lerp(grad(aa, xf, yf), grad(ba, xf - 1, yf), u);
  const x2 = lerp(grad(ab, xf, yf - 1), grad(bb, xf - 1, yf - 1), u);

  return lerp(x1, x2, v);
}

export function setNoiseSeed(seed: number): void {
  noiseSeed = seed;
  initNoisePermutation(seed);
}

export function getNoiseSeed(): number {
  return noiseSeed;
}

// Initialize with default seed
initNoisePermutation(noiseSeed);

// Get 2D noise value in range [0, 1]
export function getNoise2D(x: number, y: number, scale: number = 0.1): number {
  return (perlin2D(x * scale, y * scale) + 1) / 2;
}

// Get layered noise (octaves) for more natural variation
export function getLayeredNoise(
  x: number,
  y: number,
  octaves: number = 3,
  persistence: number = 0.5,
  scale: number = 0.1
): number {
  let total = 0;
  let frequency = scale;
  let amplitude = 1;
  let maxValue = 0;

  for (let i = 0; i < octaves; i++) {
    total += ((perlin2D(x * frequency, y * frequency) + 1) / 2) * amplitude;
    maxValue += amplitude;
    amplitude *= persistence;
    frequency *= 2;
  }

  return total / maxValue;
}

// Generate deterministic noise value for a tile (for texture variation)
export function getTileNoise(x: number, y: number): number {
  return Math.floor(getNoise2D(x, y, 0.15) * 100);
}

// =============================================================================
// NEIGHBOR UTILITIES
// =============================================================================

export interface Neighbors {
  n: boolean; // north
  s: boolean; // south
  e: boolean; // east
  w: boolean; // west
  ne: boolean; // northeast
  nw: boolean; // northwest
  se: boolean; // southeast
  sw: boolean; // southwest
}

export function getCell(grid: TileGrid, x: number, y: number): CellType {
  if (x < 0 || x >= grid[0].length || y < 0 || y >= grid.length) {
    return CELL_WALL;
  }
  return grid[y][x];
}

export function isWall(grid: TileGrid, x: number, y: number): boolean {
  return getCell(grid, x, y) === CELL_WALL;
}

export function isFloor(grid: TileGrid, x: number, y: number): boolean {
  return getCell(grid, x, y) === CELL_FLOOR;
}

export function getNeighbors(grid: TileGrid, x: number, y: number): Neighbors {
  return {
    n: isWall(grid, x, y - 1),
    s: isWall(grid, x, y + 1),
    e: isWall(grid, x + 1, y),
    w: isWall(grid, x - 1, y),
    ne: isWall(grid, x + 1, y - 1),
    nw: isWall(grid, x - 1, y - 1),
    se: isWall(grid, x + 1, y + 1),
    sw: isWall(grid, x - 1, y + 1),
  };
}

export function isWallVisible(grid: TileGrid, x: number, y: number): boolean {
  return (
    isFloor(grid, x, y - 1) ||
    isFloor(grid, x, y + 1) ||
    isFloor(grid, x + 1, y) ||
    isFloor(grid, x - 1, y) ||
    isWall(grid, x, y + 1)
  );
}

// =============================================================================
// DECAL SYSTEM TYPES
// =============================================================================

export interface DecalPlacement {
  definitionId: string;
  x: number;
  y: number;
}

export type TrackDirection = 'up' | 'down' | 'left' | 'right';

export interface TrackSegment {
  x: number;
  y: number;
  tileId: string; // Use tile name instead of coords for TUI
}

export interface TrackPath {
  segments: TrackSegment[];
}

export interface FenceSegment {
  x: number;
  y: number;
  tileId: string; // Use tile name instead of coords for TUI
}

export interface FencePlacement {
  segments: FenceSegment[];
}

// =============================================================================
// DECAL DEFINITIONS
// =============================================================================

export interface DecalDefinition {
  id: string;
  name: string;
  tiles: string[]; // Tile names from bottom to top
  width: 1 | 2;
  frequency: number;
  minSpacing: number;
  placement: 'wall_face' | 'ground' | 'wall_to_ground';
  nearWall?: boolean;
  group?: string;
}

export const DECAL_DEFINITIONS: DecalDefinition[] = [
  // Ground decals
  {
    id: 'table',
    name: 'Table',
    tiles: ['dungeon-decal-table'],
    width: 1,
    frequency: 0.003,
    minSpacing: 2,
    placement: 'ground',
  },
  {
    id: 'ground-knub',
    name: 'Ground Knub',
    tiles: ['dungeon-decal-ground-knub'],
    width: 1,
    frequency: 0.025,
    minSpacing: 3,
    placement: 'ground',
  },
  {
    id: 'ground-anvil',
    name: 'Ground Anvil',
    tiles: ['dungeon-decal-ground-anvil'],
    width: 1,
    frequency: 0.004,
    minSpacing: 2,
    placement: 'ground',
  },
  {
    id: 'ground-column',
    name: 'Ground Column',
    tiles: ['dungeon-decal-ground-column-bottom', 'dungeon-decal-ground-column-top'],
    width: 1,
    frequency: 0.01,
    minSpacing: 3,
    placement: 'ground',
    nearWall: false,
  },
  {
    id: 'chest-open-tongue',
    name: 'Chest Open Tongue',
    tiles: ['dungeon-decal-chest-open'],
    width: 1,
    frequency: 0.004,
    minSpacing: 4,
    placement: 'ground',
  },

  // Wall decals
  {
    id: 'wall_crack1',
    name: 'Wall Crack 1',
    tiles: ['dungeon-decal-wall-crack1'],
    width: 1,
    frequency: 0.04,
    minSpacing: 4,
    placement: 'wall_face',
  },
  {
    id: 'wall_crack2',
    name: 'Wall Crack 2',
    tiles: ['dungeon-decal-wall-crack2'],
    width: 1,
    frequency: 0.04,
    minSpacing: 4,
    placement: 'wall_face',
  },
  {
    id: 'wall-face-1',
    name: 'Wall Face 1',
    tiles: ['dungeon-decal-wall-face1-bottom', 'dungeon-decal-wall-face1-top'],
    width: 1,
    frequency: 0.03,
    minSpacing: 5,
    placement: 'wall_to_ground',
  },
  {
    id: 'wall-face-2',
    name: 'Wall Face 2',
    tiles: ['dungeon-decal-wall-face2-bottom', 'dungeon-decal-wall-face2-top'],
    width: 1,
    frequency: 0.03,
    minSpacing: 5,
    placement: 'wall_to_ground',
  },
  {
    id: 'wall_detail1',
    name: 'Wall Detail 1',
    tiles: ['dungeon-decal-wall-detail1'],
    width: 1,
    frequency: 0.02,
    minSpacing: 6,
    placement: 'wall_face',
  },
  {
    id: 'wall_detail2',
    name: 'Wall Detail 2',
    tiles: ['dungeon-decal-wall-detail2'],
    width: 1,
    frequency: 0.02,
    minSpacing: 6,
    placement: 'wall_face',
  },
  {
    id: 'wall_detail3',
    name: 'Wall Detail 3',
    tiles: ['dungeon-decal-wall-detail3'],
    width: 1,
    frequency: 0.02,
    minSpacing: 6,
    placement: 'wall_face',
  },

  // Pillar (wall_to_ground)
  {
    id: 'pillar',
    name: 'Stone Pillar',
    tiles: ['dungeon-decal-pillar-base', 'dungeon-decal-pillar-middle', 'dungeon-decal-pillar-crown'],
    width: 1,
    frequency: 0.008,
    minSpacing: 6,
    placement: 'wall_to_ground',
    group: 'pillar',
  },
];

export function getDecalDefinition(id: string): DecalDefinition | undefined {
  return DECAL_DEFINITIONS.find((d) => d.id === id);
}
