// Level data model + (de)serialization for SKITTERDOME.

export type Cell = [number, number];

export interface LevelData {
  version: number;
  size: Cell;
  playerSpawn: Cell;
  walls: Cell[];
  enemies: Cell[];
  turrets: Cell[];
  gigabots: Cell[]; // heavy enemy tier
  accents: Cell[]; // glowing floor tiles (decorative paint)
  tree: Cell | null; // Tree of Life objective (optional)
}

export const LS_KEY = 'skitterdome.level';

export const key = (x: number, z: number): string => `${x},${z}`;

export function defaultLevel(): LevelData {
  const w = 28;
  const h = 28;
  const walls: Cell[] = [];
  for (let x = 0; x < w; x++) walls.push([x, 0], [x, h - 1]);
  for (let z = 1; z < h - 1; z++) walls.push([0, z], [w - 1, z]);
  // corner pillar Ls
  walls.push([6, 6], [7, 6], [6, 7]);
  walls.push([21, 6], [20, 6], [21, 7]);
  walls.push([6, 21], [7, 21], [6, 22]);
  walls.push([21, 21], [20, 21], [21, 22]);
  // mid-ring gates around the center
  walls.push([12, 9], [13, 9], [12, 18], [13, 18]);
  walls.push([9, 12], [9, 13], [18, 12], [18, 13]);
  // scattered cover
  walls.push([11, 5], [16, 22], [5, 16], [25, 5], [16, 5], [11, 22], [5, 11], [22, 16]);
  return {
    version: 1,
    size: [w, h],
    playerSpawn: [5, 14],
    walls,
    // three clustered in the east so their link-lasers wire up from the start
    enemies: [
      [22, 5],
      [21, 10],
      [24, 12],
      [21, 14],
      [14, 5],
    ],
    turrets: [
      [17, 17],
      [10, 8],
    ],
    gigabots: [[24, 22]],
    tree: [14, 13],
    accents: [
      [4, 4], [8, 10], [11, 16], [14, 8], [17, 4], [23, 8],
      [3, 19], [8, 24], [13, 12], [16, 19], [20, 14], [24, 20],
      [6, 9], [10, 20], [19, 9], [23, 24], [14, 14], [4, 24],
      [24, 4], [18, 24],
    ],
  };
}

/** Parse + validate level JSON (string or already-parsed object). Throws on bad data. */
export function parseLevel(raw: unknown): LevelData {
  const data: unknown = typeof raw === 'string' ? JSON.parse(raw) : raw;
  if (!data || typeof data !== 'object' || Array.isArray(data)) {
    throw new Error('level: expected a JSON object');
  }
  const o = data as Record<string, unknown>;

  const cell = (v: unknown, name: string): Cell => {
    if (
      !Array.isArray(v) ||
      v.length !== 2 ||
      typeof v[0] !== 'number' ||
      typeof v[1] !== 'number' ||
      !Number.isFinite(v[0]) ||
      !Number.isFinite(v[1])
    ) {
      throw new Error(`level: bad cell in "${name}"`);
    }
    return [Math.floor(v[0]), Math.floor(v[1])];
  };
  const cells = (v: unknown, name: string): Cell[] => {
    if (v === undefined) return [];
    if (!Array.isArray(v)) throw new Error(`level: "${name}" must be an array`);
    return v.map((c) => cell(c, name));
  };

  const size = cell(o.size, 'size');
  if (size[0] < 4 || size[1] < 4 || size[0] > 64 || size[1] > 64) {
    throw new Error('level: size must be between 4x4 and 64x64');
  }
  const inBounds = (c: Cell): boolean => c[0] >= 0 && c[1] >= 0 && c[0] < size[0] && c[1] < size[1];

  const dedupe = (list: Cell[]): Cell[] => {
    const seen = new Set<string>();
    return list.filter((c) => {
      const k = key(c[0], c[1]);
      if (seen.has(k)) return false;
      seen.add(k);
      return true;
    });
  };

  const lvl: LevelData = {
    version: typeof o.version === 'number' ? o.version : 1,
    size,
    playerSpawn: cell(o.playerSpawn, 'playerSpawn'),
    walls: dedupe(cells(o.walls, 'walls').filter(inBounds)),
    enemies: dedupe(cells(o.enemies, 'enemies').filter(inBounds)),
    turrets: dedupe(cells(o.turrets, 'turrets').filter(inBounds)),
    gigabots: dedupe(cells(o.gigabots, 'gigabots').filter(inBounds)),
    accents: dedupe(cells(o.accents, 'accents').filter(inBounds)),
    tree: o.tree == null ? null : cell(o.tree, 'tree'),
  };
  if (!inBounds(lvl.playerSpawn)) lvl.playerSpawn = [1, 1];
  if (lvl.tree && !inBounds(lvl.tree)) lvl.tree = null;
  return lvl;
}

export const cloneLevel = (l: LevelData): LevelData => parseLevel(JSON.stringify(l));

export const levelToJson = (l: LevelData): string => JSON.stringify(l, null, 2);
