import { GameConfig } from "./config.js";
import { LootTables } from "./loot-tables.js";
import { getMutationEntries, getIncomeMultiplier } from "./mutation-rules.js";
import { getCellBiome, getBiomesCompatibleWithCell } from "./biome-rules.js";
import { cellKey } from "./grid-utils.js";
import { fillAnimalBlock, canPlaceMultiCell } from "./placement.js";
import { createSeededRng, pickId } from "./weighted-random.js";

const BIOME_TO_EGG_TYPE = { Meadow: "Color_1", Ocean: "Color_6", Mountain: "Color_11", Forest: "Color_1", Freshwater: "Color_6" };

/**
 * Loot entries for animals that match the cell biome. If the egg type has none, use the egg type for that biome.
 * @param {string} cellBiome
 * @param {Array<{ id: string, weight: number }>} loot
 * @returns {Array<{ id: string, weight: number }>}
 */
function lootForBiome(cellBiome, loot) {
  const allowedBiomes = getBiomesCompatibleWithCell(cellBiome);
  const allowed = loot.filter((entry) => {
    const def = LootTables.Animals[entry.id];
    return def && allowedBiomes.includes(def.biome);
  });
  if (allowed.length > 0) return allowed;
  const eggType = BIOME_TO_EGG_TYPE[cellBiome];
  const fallbackDef = eggType ? LootTables.EggTypes[eggType] : null;
  return fallbackDef ? fallbackDef.loot : loot;
}

/**
 * @param {string} animalId
 * @param {string} mutationId
 * @param {number} nowUnix
 * @param {{ cellsWide?: number, cellsHigh?: number }} dimensions
 * @returns {import("./types.js").AnimalCell}
 */
function buildAnimalCell(animalId, mutationId, nowUnix, dimensions = {}) {
  return {
    kind: "animal",
    id: animalId,
    mutation: mutationId,
    level: 1,
    placedAt: nowUnix,
    lastVisitedAt: nowUnix,
    lastFedAt: nowUnix,
    ...dimensions,
  };
}

/**
 * @param {import("./types.js").GameState} state
 * @param {string} key
 * @param {number} nowUnix
 * @returns {import("./types.js").EggCell | null}
 */
function getEggCellIfReady(state, key, nowUnix) {
  const cell = state.grid.cells[key];
  if (cell === null || cell === undefined || cell.kind !== "egg") return null;
  if (nowUnix < cell.hatchAt) return null;
  return cell;
}

/**
 * @param {{ state: import("./types.js").GameState, cell: import("./types.js").EggCell, x: number, y: number, nowUnix: number, eventModifiers: { mutationBonus: number } }} opts
 * @returns {{ animalData: import("./types.js").AnimalCell, w: number, h: number } | null}
 */
function getHatchAnimalData(opts) {
  const { state, cell, x, y, nowUnix, eventModifiers } = opts;
  const eggDef = LootTables.EggTypes[cell.eggType];
  if (eggDef === null || eggDef === undefined) return null;
  const cellBiome = getCellBiome(state.grid.width, state.grid.height, x, y);
  const loot = lootForBiome(cellBiome, eggDef.loot);
  if (loot.length === 0) return null;
  const rng = createSeededRng(cell.seed);
  const pickedAnimalId = pickId(rng, loot);
  const animalDef = LootTables.Animals[pickedAnimalId];
  if (animalDef === null || animalDef === undefined) return null;
  const mutationChance = GameConfig.Mutation.BaseChance + eventModifiers.mutationBonus;
  let mutationId = "none";
  if (rng() < mutationChance) mutationId = pickId(rng, getMutationEntries());
  if (getIncomeMultiplier(mutationId) === undefined) mutationId = "none";
  const w = animalDef.cellsWide ?? 1;
  const h = animalDef.cellsHigh ?? 1;
  const animalData = buildAnimalCell(pickedAnimalId, mutationId, nowUnix, { cellsWide: w, cellsHigh: h });
  return { animalData, w, h };
}

/**
 * @param {import("./types.js").GameState} state
 * @param {{ x: number, y: number, nowUnix: number, eventModifiers: { incomeMultiplier: number, mutationBonus: number } }} opts
 * @returns {boolean}
 */
export function tryHatchCell(state, opts) {
  const { x, y, nowUnix, eventModifiers } = opts;
  const key = cellKey(x, y);
  const cell = getEggCellIfReady(state, key, nowUnix);
  if (cell === null) return false;
  const eggDef = LootTables.EggTypes[cell.eggType];
  if (eggDef === null || eggDef === undefined) throw new Error("HatchingService: unknown egg type");
  const hatchData = getHatchAnimalData({ state, cell, x, y, nowUnix, eventModifiers });
  if (hatchData === null) return false;
  const { animalData, w, h } = hatchData;
  const [canPlace] = canPlaceMultiCell(state, { originX: x, originY: y, w, h, excludeOriginKey: key });
  if (!canPlace) return false;
  fillAnimalBlock(state, x, y, animalData);
  return true;
}

/**
 * @param {import("./types.js").GameState} state
 * @param {number} nowUnix
 * @param {{ incomeMultiplier: number, mutationBonus: number }} eventModifiers
 * @returns {{ changed: boolean, hatched: Array<{ x: number, y: number }> }}
 */
export function run(state, nowUnix, eventModifiers) {
  const hatched = [];
  const keysToProcess = [];
  for (const [key, cell] of Object.entries(state.grid.cells)) {
    if (cell.kind === "egg" && nowUnix >= cell.hatchAt) keysToProcess.push(key);
  }
  for (const key of keysToProcess) {
    const m = key.match(/^(\d+)_(\d+)$/);
    if (m) {
      const x = Number(m[1]);
      const y = Number(m[2]);
      const didHatch = tryHatchCell(state, { x, y, nowUnix, eventModifiers });
      if (didHatch && state.grid.cells[cellKey(x, y)]?.kind === "animal") hatched.push({ x, y });
    }
  }
  return { changed: hatched.length > 0, hatched };
}
